{
  "config": {
    "configFile": "/var/www/360lm/playwright.config.js",
    "rootDir": "/var/www/360lm/tests",
    "forbidOnly": false,
    "fullyParallel": false,
    "globalSetup": null,
    "globalTeardown": null,
    "globalTimeout": 0,
    "grep": {},
    "grepInvert": null,
    "maxFailures": 0,
    "metadata": {
      "actualWorkers": 1
    },
    "preserveOutput": "always",
    "projects": [
      {
        "outputDir": "/var/www/360lm/test-results",
        "repeatEach": 1,
        "retries": 1,
        "metadata": {
          "actualWorkers": 1
        },
        "id": "android-chrome",
        "name": "android-chrome",
        "testDir": "/var/www/360lm/tests",
        "testIgnore": [],
        "testMatch": [
          "**/*.@(spec|test).?(c|m)[jt]s?(x)"
        ],
        "timeout": 30000
      },
      {
        "outputDir": "/var/www/360lm/test-results",
        "repeatEach": 1,
        "retries": 1,
        "metadata": {
          "actualWorkers": 1
        },
        "id": "desktop-chrome",
        "name": "desktop-chrome",
        "testDir": "/var/www/360lm/tests",
        "testIgnore": [],
        "testMatch": [
          "**/*.@(spec|test).?(c|m)[jt]s?(x)"
        ],
        "timeout": 30000
      }
    ],
    "quiet": false,
    "reporter": [
      [
        "json",
        {
          "outputFile": "test_reports/last_run.json"
        }
      ],
      [
        "line",
        null
      ]
    ],
    "reportSlowTests": {
      "max": 5,
      "threshold": 300000
    },
    "shard": null,
    "tags": [],
    "updateSnapshots": "missing",
    "updateSourceMethod": "patch",
    "version": "1.60.0",
    "workers": 1,
    "webServer": null
  },
  "suites": [
    {
      "title": "activity.spec.js",
      "file": "activity.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Activity — Smoke",
          "file": "activity.spec.js",
          "line": 10,
          "column": 6,
          "specs": [
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2293,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:15.121Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-45bb9da2781273921e33",
              "file": "activity.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 616,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:19.494Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-d6284674306e1787a594",
              "file": "activity.spec.js",
              "line": 18,
              "column": 3
            },
            {
              "title": "sw.js contains current version",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 325,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.197Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-bea7455145b8c2c4d860",
              "file": "activity.spec.js",
              "line": 23,
              "column": 3
            },
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1245,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:06.883Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-0f05c1db7f936b1770cf",
              "file": "activity.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 198,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:08.363Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-2112e78eca790609920b",
              "file": "activity.spec.js",
              "line": 18,
              "column": 3
            },
            {
              "title": "sw.js contains current version",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 204,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:08.585Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-a830c832290db1660126",
              "file": "activity.spec.js",
              "line": 23,
              "column": 3
            }
          ]
        },
        {
          "title": "Activity — HTML Structure (core)",
          "file": "activity.spec.js",
          "line": 31,
          "column": 6,
          "specs": [
            {
              "title": "capture screen and submit/draft buttons present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 77,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.558Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-77f84d04d074f2d70a94",
              "file": "activity.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "response list and presentation screens present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 32,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.655Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-3c3440e71d456b4fbe8a",
              "file": "activity.spec.js",
              "line": 47,
              "column": 3
            },
            {
              "title": "capture screen and submit/draft buttons present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 82,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:08.813Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-7610f56fa1f0287f3249",
              "file": "activity.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "response list and presentation screens present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 27,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:08.911Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-a28fb01019a5d12e16a6",
              "file": "activity.spec.js",
              "line": 47,
              "column": 3
            }
          ]
        },
        {
          "title": "Activity — HTML Structure (v21 draft datetime)",
          "file": "activity.spec.js",
          "line": 62,
          "column": 6,
          "specs": [
            {
              "title": "submit-datetime modal elements present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 26,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.704Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-16370f92dcc0d957eeb0",
              "file": "activity.spec.js",
              "line": 63,
              "column": 3
            },
            {
              "title": "IST datetime helper functions present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 18,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.755Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-7fc08d7e1d426a968e27",
              "file": "activity.spec.js",
              "line": 78,
              "column": 3
            },
            {
              "title": "submission datetime picker flow functions present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 15,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.787Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-ae0062346cb3397cc1f4",
              "file": "activity.spec.js",
              "line": 91,
              "column": 3
            },
            {
              "title": "audit trail CSS and audit panel in buildPresentation present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 22,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.814Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-c3260fe8ff436c5cd715",
              "file": "activity.spec.js",
              "line": 105,
              "column": 3
            },
            {
              "title": "draft PATCH includes submitted_at and error guard",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 18,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.852Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-c63db10b1de7f63715bf",
              "file": "activity.spec.js",
              "line": 121,
              "column": 3
            },
            {
              "title": "submit-datetime modal elements present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 20,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:08.951Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-3204473521eed1129c8c",
              "file": "activity.spec.js",
              "line": 63,
              "column": 3
            },
            {
              "title": "IST datetime helper functions present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 12,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:08.985Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-a7c7cb170591b9ae6d16",
              "file": "activity.spec.js",
              "line": 78,
              "column": 3
            },
            {
              "title": "submission datetime picker flow functions present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 18,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.009Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-124f71d700a952519bbd",
              "file": "activity.spec.js",
              "line": 91,
              "column": 3
            },
            {
              "title": "audit trail CSS and audit panel in buildPresentation present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 21,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.038Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-aadb3c0f20eca04815f5",
              "file": "activity.spec.js",
              "line": 105,
              "column": 3
            },
            {
              "title": "draft PATCH includes submitted_at and error guard",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 17,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.071Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-7c8b03df43309d0e3ef7",
              "file": "activity.spec.js",
              "line": 121,
              "column": 3
            }
          ]
        },
        {
          "title": "Activity — HTML Structure (v23 cascading filters)",
          "file": "activity.spec.js",
          "line": 134,
          "column": 6,
          "specs": [
            {
              "title": "audit trail check uses ADMIN_IDS not isAdmin",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.887Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-05066d6a9f247b0e561b",
              "file": "activity.spec.js",
              "line": 135,
              "column": 3
            },
            {
              "title": "draft-btn text reset in buildCaptureForm",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.911Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-22f8c98b04c7f4d09112",
              "file": "activity.spec.js",
              "line": 146,
              "column": 3
            },
            {
              "title": "client-side min-date validation in confirmSubmitDatetime",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.938Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-baba157449c608af4acd",
              "file": "activity.spec.js",
              "line": 156,
              "column": 3
            },
            {
              "title": "server error message extracted from PostgREST JSON",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 12,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.959Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-83c8b0c6216f28424f1f",
              "file": "activity.spec.js",
              "line": 167,
              "column": 3
            },
            {
              "title": "hint text uses human-readable format",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 16,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:20.982Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-5b7638844423a2799628",
              "file": "activity.spec.js",
              "line": 177,
              "column": 3
            },
            {
              "title": "cascading filter — computeAvailable and matchesFiltersExcept present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 12,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:21.011Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-d98e836565a5867d0c7e",
              "file": "activity.spec.js",
              "line": 188,
              "column": 3
            },
            {
              "title": "cascading filter — status split into Lead Type + Submission rows",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 12,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:21.034Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-dccccd0142bf3cf29769",
              "file": "activity.spec.js",
              "line": 199,
              "column": 3
            },
            {
              "title": "cascading filter — sequence badge CSS present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:21.057Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-5c6d3c53b52be256b4c1",
              "file": "activity.spec.js",
              "line": 213,
              "column": 3
            },
            {
              "title": "cascading filter — stats panel is filter-aware",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:21.081Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-fd3da6eb4749870ff2aa",
              "file": "activity.spec.js",
              "line": 225,
              "column": 3
            },
            {
              "title": "audit trail check uses ADMIN_IDS not isAdmin",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.102Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-c8befe1e0a8dda0871e7",
              "file": "activity.spec.js",
              "line": 135,
              "column": 3
            },
            {
              "title": "draft-btn text reset in buildCaptureForm",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 12,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.125Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-906574b62a27c3eb7f7a",
              "file": "activity.spec.js",
              "line": 146,
              "column": 3
            },
            {
              "title": "client-side min-date validation in confirmSubmitDatetime",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.150Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-c7b6ed3fd63d74e0b6ae",
              "file": "activity.spec.js",
              "line": 156,
              "column": 3
            },
            {
              "title": "server error message extracted from PostgREST JSON",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.170Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-883777665f5cc6cca6a0",
              "file": "activity.spec.js",
              "line": 167,
              "column": 3
            },
            {
              "title": "hint text uses human-readable format",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.192Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-b3520d121514076d339a",
              "file": "activity.spec.js",
              "line": 177,
              "column": 3
            },
            {
              "title": "cascading filter — computeAvailable and matchesFiltersExcept present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.214Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-b67c6919a96c2ec3c1a9",
              "file": "activity.spec.js",
              "line": 188,
              "column": 3
            },
            {
              "title": "cascading filter — status split into Lead Type + Submission rows",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 21,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.241Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-5064cb61a86a4e40bf45",
              "file": "activity.spec.js",
              "line": 199,
              "column": 3
            },
            {
              "title": "cascading filter — sequence badge CSS present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.274Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-e6d9f0118ec03ce3639d",
              "file": "activity.spec.js",
              "line": 213,
              "column": 3
            },
            {
              "title": "cascading filter — stats panel is filter-aware",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.293Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-d74f8e534587325d728b",
              "file": "activity.spec.js",
              "line": 225,
              "column": 3
            }
          ]
        },
        {
          "title": "Activity — Hub Navigation",
          "file": "activity.spec.js",
          "line": 238,
          "column": 6,
          "specs": [
            {
              "title": "home topbar has Hub button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:21.106Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-00d54e0526426c598f17",
              "file": "activity.spec.js",
              "line": 239,
              "column": 3
            },
            {
              "title": "sw.js is v25 or later",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:21.135Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-09bfb43732ec0dd0e518",
              "file": "activity.spec.js",
              "line": 249,
              "column": 3
            },
            {
              "title": "home topbar has Hub button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 9,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.316Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-455a5260a3cc4c799541",
              "file": "activity.spec.js",
              "line": 239,
              "column": 3
            },
            {
              "title": "sw.js is v25 or later",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.333Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-3333643baa7542543de2",
              "file": "activity.spec.js",
              "line": 249,
              "column": 3
            }
          ]
        },
        {
          "title": "Activity — Delete Activity",
          "file": "activity.spec.js",
          "line": 259,
          "column": 6,
          "specs": [
            {
              "title": "HTML carries delete modal + admin-only handlers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 518,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:21.161Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-4558cb916bfbc08776a6",
              "file": "activity.spec.js",
              "line": 270,
              "column": 3
            },
            {
              "title": "full lifecycle: deactivate → reactivate → hard delete (0 responses)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1595,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:21.704Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-f22739fad5018f146e33",
              "file": "activity.spec.js",
              "line": 279,
              "column": 3
            },
            {
              "title": "activity with responses cannot be hard-deleted from the modal",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 538,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:23.315Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-da990248267dba8b4a29",
              "file": "activity.spec.js",
              "line": 312,
              "column": 3
            },
            {
              "title": "non-admin sees no delete button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2744,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:23.872Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-95f0a25a4bf8e98a2975",
              "file": "activity.spec.js",
              "line": 323,
              "column": 3
            },
            {
              "title": "HTML carries delete modal + admin-only handlers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 312,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.354Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-24df01e65b24651002da",
              "file": "activity.spec.js",
              "line": 270,
              "column": 3
            },
            {
              "title": "full lifecycle: deactivate → reactivate → hard delete (0 responses)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1084,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:09.688Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-d51fae2e1b3667678f89",
              "file": "activity.spec.js",
              "line": 279,
              "column": 3
            },
            {
              "title": "activity with responses cannot be hard-deleted from the modal",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 460,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:10.793Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-3f34dc2cd4f8fda4f30b",
              "file": "activity.spec.js",
              "line": 312,
              "column": 3
            },
            {
              "title": "non-admin sees no delete button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2773,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:11.273Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-4e58c359d947ad771bdf",
              "file": "activity.spec.js",
              "line": 323,
              "column": 3
            }
          ]
        },
        {
          "title": "Activity — Tutorial",
          "file": "activity.spec.js",
          "line": 336,
          "column": 6,
          "specs": [
            {
              "title": "tutorial page + both videos are served",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 598,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:26.637Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-9128c584a48aeb05efc2",
              "file": "activity.spec.js",
              "line": 337,
              "column": 3
            },
            {
              "title": "settings sheet links to the tutorial; learn catalog lists it",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 18,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:27.246Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-5f815119f96a60eafd7e",
              "file": "activity.spec.js",
              "line": 348,
              "column": 3
            },
            {
              "title": "tutorial page + both videos are served",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 207,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:14.060Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-97e146be5a401e85b301",
              "file": "activity.spec.js",
              "line": 337,
              "column": 3
            },
            {
              "title": "settings sheet links to the tutorial; learn catalog lists it",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 20,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:14.285Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-808148ae71b4fdeeb21a",
              "file": "activity.spec.js",
              "line": 348,
              "column": 3
            }
          ]
        },
        {
          "title": "Activity — GForm-first creation",
          "file": "activity.spec.js",
          "line": 360,
          "column": 6,
          "specs": [
            {
              "title": "new-activity opens on the Form step with details hidden; blank path reveals form with default dates",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 780,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:27.275Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-11f599ab43c529d65f4b",
              "file": "activity.spec.js",
              "line": 367,
              "column": 3
            },
            {
              "title": "auto-fill from a real GForm pre-fills title, ID, city, dates and fields",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1883,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:28.075Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-5ec365a017d281cac2a2",
              "file": "activity.spec.js",
              "line": 379,
              "column": 3
            },
            {
              "title": "empty link shows inline guidance, not a crash",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 435,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:29.973Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-d26633776c2d6457255c",
              "file": "activity.spec.js",
              "line": 392,
              "column": 3
            },
            {
              "title": "new-activity opens on the Form step with details hidden; blank path reveals form with default dates",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 457,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:14.317Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-2c5d5ad6d22ec7b4453a",
              "file": "activity.spec.js",
              "line": 367,
              "column": 3
            },
            {
              "title": "auto-fill from a real GForm pre-fills title, ID, city, dates and fields",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1319,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:14.788Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-362a84b4583d9319f9c6",
              "file": "activity.spec.js",
              "line": 379,
              "column": 3
            },
            {
              "title": "empty link shows inline guidance, not a crash",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 467,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:16.128Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "df521ef6570605d13829-6c480378c27610de50c0",
              "file": "activity.spec.js",
              "line": 392,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "admin.spec.js",
      "file": "admin.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Admin — Smoke",
          "file": "admin.spec.js",
          "line": 10,
          "column": 6,
          "specs": [
            {
              "title": "page loads with login screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 0,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8212,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:15:46",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 46,
                          "line": 15
                        },
                        "snippet": "  13 |     const body = await page.locator('body').innerText();\n  14 |     expect(body.length).toBeGreaterThan(0);\n> 15 |     await expect(page.locator('#scr-login')).toBeVisible({ timeout: 8000 });\n     |                                              ^\n  16 |   });\n  17 |\n  18 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 46,
                            "line": 15
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n\n\n  13 |     const body = await page.locator('body').innerText();\n  14 |     expect(body.length).toBeGreaterThan(0);\n> 15 |     await expect(page.locator('#scr-login')).toBeVisible({ timeout: 8000 });\n     |                                              ^\n  16 |   });\n  17 |\n  18 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:15:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:30.441Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-page-loads-with-login-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-page-loads-with-login-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 46,
                        "line": 15
                      }
                    },
                    {
                      "workerIndex": 1,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8330,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:15:46",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 46,
                          "line": 15
                        },
                        "snippet": "  13 |     const body = await page.locator('body').innerText();\n  14 |     expect(body.length).toBeGreaterThan(0);\n> 15 |     await expect(page.locator('#scr-login')).toBeVisible({ timeout: 8000 });\n     |                                              ^\n  16 |   });\n  17 |\n  18 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 46,
                            "line": 15
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n\n\n  13 |     const body = await page.locator('body').innerText();\n  14 |     expect(body.length).toBeGreaterThan(0);\n> 15 |     await expect(page.locator('#scr-login')).toBeVisible({ timeout: 8000 });\n     |                                              ^\n  16 |   });\n  17 |\n  18 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:15:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:45:39.501Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-page-loads-with-login-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-page-loads-with-login-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 46,
                        "line": 15
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-2e7998d85724cf3388e6",
              "file": "admin.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 2,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 353,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:20:28",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 28,
                          "line": 20
                        },
                        "snippet": "  18 |   test('manifest.json returns 200', async ({ page }) => {\n  19 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 20 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  21 |   });\n  22 |\n  23 |   test('sw.js contains current version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 28,
                            "line": 20
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  18 |   test('manifest.json returns 200', async ({ page }) => {\n  19 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 20 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  21 |   });\n  22 |\n  23 |   test('sw.js contains current version', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:20:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:48.831Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 28,
                        "line": 20
                      }
                    },
                    {
                      "workerIndex": 3,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 305,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:20:28",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 28,
                          "line": 20
                        },
                        "snippet": "  18 |   test('manifest.json returns 200', async ({ page }) => {\n  19 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 20 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  21 |   });\n  22 |\n  23 |   test('sw.js contains current version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 28,
                            "line": 20
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  18 |   test('manifest.json returns 200', async ({ page }) => {\n  19 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 20 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  21 |   });\n  22 |\n  23 |   test('sw.js contains current version', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:20:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:45:50.479Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 28,
                        "line": 20
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-88be95d030aa1ef4aff7",
              "file": "admin.spec.js",
              "line": 18,
              "column": 3
            },
            {
              "title": "sw.js contains current version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 4,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 266,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:26:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 26
                        },
                        "snippet": "  24 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  25 |     const text = await resp?.text() || '';\n> 26 |     expect(text).toMatch(/360admin-v\\d+/);\n     |                  ^\n  27 |   });\n  28 | });\n  29 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 26
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  24 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  25 |     const text = await resp?.text() || '';\n> 26 |     expect(text).toMatch(/360admin-v\\d+/);\n     |                  ^\n  27 |   });\n  28 | });\n  29 |\n    at /var/www/360lm/tests/admin.spec.js:26:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:51.842Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-sw-js-contains-current-version-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-sw-js-contains-current-version-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 26
                      }
                    },
                    {
                      "workerIndex": 5,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 272,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:26:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 26
                        },
                        "snippet": "  24 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  25 |     const text = await resp?.text() || '';\n> 26 |     expect(text).toMatch(/360admin-v\\d+/);\n     |                  ^\n  27 |   });\n  28 | });\n  29 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 26
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  24 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  25 |     const text = await resp?.text() || '';\n> 26 |     expect(text).toMatch(/360admin-v\\d+/);\n     |                  ^\n  27 |   });\n  28 | });\n  29 |\n    at /var/www/360lm/tests/admin.spec.js:26:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:45:53.119Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-sw-js-contains-current-version-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-sw-js-contains-current-version-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 26
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-2c2c51b9b7de2a036f80",
              "file": "admin.spec.js",
              "line": 23,
              "column": 3
            },
            {
              "title": "page loads with login screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1007,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8192,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:15:46",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 46,
                          "line": 15
                        },
                        "snippet": "  13 |     const body = await page.locator('body').innerText();\n  14 |     expect(body.length).toBeGreaterThan(0);\n> 15 |     await expect(page.locator('#scr-login')).toBeVisible({ timeout: 8000 });\n     |                                              ^\n  16 |   });\n  17 |\n  18 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 46,
                            "line": 15
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n\n\n  13 |     const body = await page.locator('body').innerText();\n  14 |     expect(body.length).toBeGreaterThan(0);\n> 15 |     await expect(page.locator('#scr-login')).toBeVisible({ timeout: 8000 });\n     |                                              ^\n  16 |   });\n  17 |\n  18 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:15:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:16.623Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-page-loads-with-login-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-page-loads-with-login-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 46,
                        "line": 15
                      }
                    },
                    {
                      "workerIndex": 1008,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8269,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:15:46",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 46,
                          "line": 15
                        },
                        "snippet": "  13 |     const body = await page.locator('body').innerText();\n  14 |     expect(body.length).toBeGreaterThan(0);\n> 15 |     await expect(page.locator('#scr-login')).toBeVisible({ timeout: 8000 });\n     |                                              ^\n  16 |   });\n  17 |\n  18 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 46,
                            "line": 15
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-login')\u001b[22m\n\n\n  13 |     const body = await page.locator('body').innerText();\n  14 |     expect(body.length).toBeGreaterThan(0);\n> 15 |     await expect(page.locator('#scr-login')).toBeVisible({ timeout: 8000 });\n     |                                              ^\n  16 |   });\n  17 |\n  18 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:15:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:45:25.720Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-page-loads-with-login-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-page-loads-with-login-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 46,
                        "line": 15
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-5b2b7da8ebd71413d5b7",
              "file": "admin.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1009,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 320,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:20:28",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 28,
                          "line": 20
                        },
                        "snippet": "  18 |   test('manifest.json returns 200', async ({ page }) => {\n  19 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 20 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  21 |   });\n  22 |\n  23 |   test('sw.js contains current version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 28,
                            "line": 20
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  18 |   test('manifest.json returns 200', async ({ page }) => {\n  19 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 20 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  21 |   });\n  22 |\n  23 |   test('sw.js contains current version', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:20:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:34.998Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 28,
                        "line": 20
                      }
                    },
                    {
                      "workerIndex": 1010,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 279,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:20:28",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 28,
                          "line": 20
                        },
                        "snippet": "  18 |   test('manifest.json returns 200', async ({ page }) => {\n  19 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 20 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  21 |   });\n  22 |\n  23 |   test('sw.js contains current version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 28,
                            "line": 20
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  18 |   test('manifest.json returns 200', async ({ page }) => {\n  19 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 20 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  21 |   });\n  22 |\n  23 |   test('sw.js contains current version', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:20:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:45:36.288Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 28,
                        "line": 20
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-156f52b806b66ccd5eb5",
              "file": "admin.spec.js",
              "line": 18,
              "column": 3
            },
            {
              "title": "sw.js contains current version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1011,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 294,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:26:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 26
                        },
                        "snippet": "  24 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  25 |     const text = await resp?.text() || '';\n> 26 |     expect(text).toMatch(/360admin-v\\d+/);\n     |                  ^\n  27 |   });\n  28 | });\n  29 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 26
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  24 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  25 |     const text = await resp?.text() || '';\n> 26 |     expect(text).toMatch(/360admin-v\\d+/);\n     |                  ^\n  27 |   });\n  28 | });\n  29 |\n    at /var/www/360lm/tests/admin.spec.js:26:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:37.709Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-sw-js-contains-current-version-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-sw-js-contains-current-version-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 26
                      }
                    },
                    {
                      "workerIndex": 1012,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 289,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:26:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 26
                        },
                        "snippet": "  24 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  25 |     const text = await resp?.text() || '';\n> 26 |     expect(text).toMatch(/360admin-v\\d+/);\n     |                  ^\n  27 |   });\n  28 | });\n  29 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 26
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360admin-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  24 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  25 |     const text = await resp?.text() || '';\n> 26 |     expect(text).toMatch(/360admin-v\\d+/);\n     |                  ^\n  27 |   });\n  28 | });\n  29 |\n    at /var/www/360lm/tests/admin.spec.js:26:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:45:38.969Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-sw-js-contains-current-version-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Smoke-sw-js-contains-current-version-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 26
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-b7ed8c1ab7be327d3b09",
              "file": "admin.spec.js",
              "line": 23,
              "column": 3
            }
          ]
        },
        {
          "title": "Admin — HTML Structure",
          "file": "admin.spec.js",
          "line": 31,
          "column": 6,
          "specs": [
            {
              "title": "Client Zones tile exists on home screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 6,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:36:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 36
                        },
                        "snippet": "  34 |     const resp = await ctx.get(BASE_URL);\n  35 |     const html = await resp.text();\n> 36 |     expect(html).toContain('Client Zones');\n     |                  ^\n  37 |     expect(html).toContain('showZones()');\n  38 |     expect(html).toContain('Zones &amp; city mapping');\n  39 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  34 |     const resp = await ctx.get(BASE_URL);\n  35 |     const html = await resp.text();\n> 36 |     expect(html).toContain('Client Zones');\n     |                  ^\n  37 |     expect(html).toContain('showZones()');\n  38 |     expect(html).toContain('Zones &amp; city mapping');\n  39 |   });\n    at /var/www/360lm/tests/admin.spec.js:36:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:54.528Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-5688a--tile-exists-on-home-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 36
                      }
                    },
                    {
                      "workerIndex": 7,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:36:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 36
                        },
                        "snippet": "  34 |     const resp = await ctx.get(BASE_URL);\n  35 |     const html = await resp.text();\n> 36 |     expect(html).toContain('Client Zones');\n     |                  ^\n  37 |     expect(html).toContain('showZones()');\n  38 |     expect(html).toContain('Zones &amp; city mapping');\n  39 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  34 |     const resp = await ctx.get(BASE_URL);\n  35 |     const html = await resp.text();\n> 36 |     expect(html).toContain('Client Zones');\n     |                  ^\n  37 |     expect(html).toContain('showZones()');\n  38 |     expect(html).toContain('Zones &amp; city mapping');\n  39 |   });\n    at /var/www/360lm/tests/admin.spec.js:36:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:45:55.451Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-5688a--tile-exists-on-home-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 36
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-fb639d3f7a341687d175",
              "file": "admin.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "zones screen and modal exist in DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 8,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |     const resp = await ctx.get(BASE_URL);\n  44 |     const html = await resp.text();\n> 45 |     expect(html).toContain('id=\"scr-zones\"');\n     |                  ^\n  46 |     expect(html).toContain('id=\"zones-list\"');\n  47 |     expect(html).toContain('id=\"modal-zone\"');\n  48 |     expect(html).toContain('id=\"zone-name-input\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |     const resp = await ctx.get(BASE_URL);\n  44 |     const html = await resp.text();\n> 45 |     expect(html).toContain('id=\"scr-zones\"');\n     |                  ^\n  46 |     expect(html).toContain('id=\"zones-list\"');\n  47 |     expect(html).toContain('id=\"modal-zone\"');\n  48 |     expect(html).toContain('id=\"zone-name-input\"');\n    at /var/www/360lm/tests/admin.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:56.330Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-0c1f7-reen-and-modal-exist-in-DOM-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    },
                    {
                      "workerIndex": 9,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |     const resp = await ctx.get(BASE_URL);\n  44 |     const html = await resp.text();\n> 45 |     expect(html).toContain('id=\"scr-zones\"');\n     |                  ^\n  46 |     expect(html).toContain('id=\"zones-list\"');\n  47 |     expect(html).toContain('id=\"modal-zone\"');\n  48 |     expect(html).toContain('id=\"zone-name-input\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |     const resp = await ctx.get(BASE_URL);\n  44 |     const html = await resp.text();\n> 45 |     expect(html).toContain('id=\"scr-zones\"');\n     |                  ^\n  46 |     expect(html).toContain('id=\"zones-list\"');\n  47 |     expect(html).toContain('id=\"modal-zone\"');\n  48 |     expect(html).toContain('id=\"zone-name-input\"');\n    at /var/www/360lm/tests/admin.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:45:57.204Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-0c1f7-reen-and-modal-exist-in-DOM-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-23e73c632164a1c90343",
              "file": "admin.spec.js",
              "line": 41,
              "column": 3
            },
            {
              "title": "city chip CSS classes present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 10,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:57:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 57
                        },
                        "snippet": "  55 |     const resp = await ctx.get(BASE_URL);\n  56 |     const html = await resp.text();\n> 57 |     expect(html).toContain('.city-chip');\n     |                  ^\n  58 |     expect(html).toContain('.city-chip-del');\n  59 |     expect(html).toContain('.city-chips-wrap');\n  60 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 57
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  55 |     const resp = await ctx.get(BASE_URL);\n  56 |     const html = await resp.text();\n> 57 |     expect(html).toContain('.city-chip');\n     |                  ^\n  58 |     expect(html).toContain('.city-chip-del');\n  59 |     expect(html).toContain('.city-chips-wrap');\n  60 |   });\n    at /var/www/360lm/tests/admin.spec.js:57:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:45:58.133Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structure-city-chip-CSS-classes-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 57
                      }
                    },
                    {
                      "workerIndex": 11,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 76,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:57:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 57
                        },
                        "snippet": "  55 |     const resp = await ctx.get(BASE_URL);\n  56 |     const html = await resp.text();\n> 57 |     expect(html).toContain('.city-chip');\n     |                  ^\n  58 |     expect(html).toContain('.city-chip-del');\n  59 |     expect(html).toContain('.city-chips-wrap');\n  60 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 57
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  55 |     const resp = await ctx.get(BASE_URL);\n  56 |     const html = await resp.text();\n> 57 |     expect(html).toContain('.city-chip');\n     |                  ^\n  58 |     expect(html).toContain('.city-chip-del');\n  59 |     expect(html).toContain('.city-chips-wrap');\n  60 |   });\n    at /var/www/360lm/tests/admin.spec.js:57:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:45:59.129Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structure-city-chip-CSS-classes-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 57
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-3845955eb10f91cc8e11",
              "file": "admin.spec.js",
              "line": 53,
              "column": 3
            },
            {
              "title": "existing screens still present (no regression)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 12,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 76,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:66:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 66
                        },
                        "snippet": "  64 |     const resp = await ctx.get(BASE_URL);\n  65 |     const html = await resp.text();\n> 66 |     expect(html).toContain('id=\"scr-companies\"');\n     |                  ^\n  67 |     expect(html).toContain('id=\"scr-employees\"');\n  68 |     expect(html).toContain('id=\"scr-access\"');\n  69 |     expect(html).toContain('id=\"scr-pwa-registry\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 66
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  64 |     const resp = await ctx.get(BASE_URL);\n  65 |     const html = await resp.text();\n> 66 |     expect(html).toContain('id=\"scr-companies\"');\n     |                  ^\n  67 |     expect(html).toContain('id=\"scr-employees\"');\n  68 |     expect(html).toContain('id=\"scr-access\"');\n  69 |     expect(html).toContain('id=\"scr-pwa-registry\"');\n    at /var/www/360lm/tests/admin.spec.js:66:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:00.136Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-cc0ca-till-present-no-regression--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 66
                      }
                    },
                    {
                      "workerIndex": 13,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 94,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:66:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 66
                        },
                        "snippet": "  64 |     const resp = await ctx.get(BASE_URL);\n  65 |     const html = await resp.text();\n> 66 |     expect(html).toContain('id=\"scr-companies\"');\n     |                  ^\n  67 |     expect(html).toContain('id=\"scr-employees\"');\n  68 |     expect(html).toContain('id=\"scr-access\"');\n  69 |     expect(html).toContain('id=\"scr-pwa-registry\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 66
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  64 |     const resp = await ctx.get(BASE_URL);\n  65 |     const html = await resp.text();\n> 66 |     expect(html).toContain('id=\"scr-companies\"');\n     |                  ^\n  67 |     expect(html).toContain('id=\"scr-employees\"');\n  68 |     expect(html).toContain('id=\"scr-access\"');\n  69 |     expect(html).toContain('id=\"scr-pwa-registry\"');\n    at /var/www/360lm/tests/admin.spec.js:66:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:01.226Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-cc0ca-till-present-no-regression--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 66
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-84c21a954ca532cb7d1c",
              "file": "admin.spec.js",
              "line": 62,
              "column": 3
            },
            {
              "title": "Client Zones tile exists on home screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1013,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:36:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 36
                        },
                        "snippet": "  34 |     const resp = await ctx.get(BASE_URL);\n  35 |     const html = await resp.text();\n> 36 |     expect(html).toContain('Client Zones');\n     |                  ^\n  37 |     expect(html).toContain('showZones()');\n  38 |     expect(html).toContain('Zones &amp; city mapping');\n  39 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  34 |     const resp = await ctx.get(BASE_URL);\n  35 |     const html = await resp.text();\n> 36 |     expect(html).toContain('Client Zones');\n     |                  ^\n  37 |     expect(html).toContain('showZones()');\n  38 |     expect(html).toContain('Zones &amp; city mapping');\n  39 |   });\n    at /var/www/360lm/tests/admin.spec.js:36:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:40.270Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-5688a--tile-exists-on-home-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 36
                      }
                    },
                    {
                      "workerIndex": 1014,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:36:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 36
                        },
                        "snippet": "  34 |     const resp = await ctx.get(BASE_URL);\n  35 |     const html = await resp.text();\n> 36 |     expect(html).toContain('Client Zones');\n     |                  ^\n  37 |     expect(html).toContain('showZones()');\n  38 |     expect(html).toContain('Zones &amp; city mapping');\n  39 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Client Zones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  34 |     const resp = await ctx.get(BASE_URL);\n  35 |     const html = await resp.text();\n> 36 |     expect(html).toContain('Client Zones');\n     |                  ^\n  37 |     expect(html).toContain('showZones()');\n  38 |     expect(html).toContain('Zones &amp; city mapping');\n  39 |   });\n    at /var/www/360lm/tests/admin.spec.js:36:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:45:41.135Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-5688a--tile-exists-on-home-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 36
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-c803be35369ea32666c1",
              "file": "admin.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "zones screen and modal exist in DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1015,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |     const resp = await ctx.get(BASE_URL);\n  44 |     const html = await resp.text();\n> 45 |     expect(html).toContain('id=\"scr-zones\"');\n     |                  ^\n  46 |     expect(html).toContain('id=\"zones-list\"');\n  47 |     expect(html).toContain('id=\"modal-zone\"');\n  48 |     expect(html).toContain('id=\"zone-name-input\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |     const resp = await ctx.get(BASE_URL);\n  44 |     const html = await resp.text();\n> 45 |     expect(html).toContain('id=\"scr-zones\"');\n     |                  ^\n  46 |     expect(html).toContain('id=\"zones-list\"');\n  47 |     expect(html).toContain('id=\"modal-zone\"');\n  48 |     expect(html).toContain('id=\"zone-name-input\"');\n    at /var/www/360lm/tests/admin.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:42.045Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-0c1f7-reen-and-modal-exist-in-DOM-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    },
                    {
                      "workerIndex": 1016,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |     const resp = await ctx.get(BASE_URL);\n  44 |     const html = await resp.text();\n> 45 |     expect(html).toContain('id=\"scr-zones\"');\n     |                  ^\n  46 |     expect(html).toContain('id=\"zones-list\"');\n  47 |     expect(html).toContain('id=\"modal-zone\"');\n  48 |     expect(html).toContain('id=\"zone-name-input\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-zones\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |     const resp = await ctx.get(BASE_URL);\n  44 |     const html = await resp.text();\n> 45 |     expect(html).toContain('id=\"scr-zones\"');\n     |                  ^\n  46 |     expect(html).toContain('id=\"zones-list\"');\n  47 |     expect(html).toContain('id=\"modal-zone\"');\n  48 |     expect(html).toContain('id=\"zone-name-input\"');\n    at /var/www/360lm/tests/admin.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:45:42.958Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-0c1f7-reen-and-modal-exist-in-DOM-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-8e10d1340b66b524e3db",
              "file": "admin.spec.js",
              "line": 41,
              "column": 3
            },
            {
              "title": "city chip CSS classes present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1017,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:57:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 57
                        },
                        "snippet": "  55 |     const resp = await ctx.get(BASE_URL);\n  56 |     const html = await resp.text();\n> 57 |     expect(html).toContain('.city-chip');\n     |                  ^\n  58 |     expect(html).toContain('.city-chip-del');\n  59 |     expect(html).toContain('.city-chips-wrap');\n  60 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 57
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  55 |     const resp = await ctx.get(BASE_URL);\n  56 |     const html = await resp.text();\n> 57 |     expect(html).toContain('.city-chip');\n     |                  ^\n  58 |     expect(html).toContain('.city-chip-del');\n  59 |     expect(html).toContain('.city-chips-wrap');\n  60 |   });\n    at /var/www/360lm/tests/admin.spec.js:57:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:43.834Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structure-city-chip-CSS-classes-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 57
                      }
                    },
                    {
                      "workerIndex": 1018,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:57:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 57
                        },
                        "snippet": "  55 |     const resp = await ctx.get(BASE_URL);\n  56 |     const html = await resp.text();\n> 57 |     expect(html).toContain('.city-chip');\n     |                  ^\n  58 |     expect(html).toContain('.city-chip-del');\n  59 |     expect(html).toContain('.city-chips-wrap');\n  60 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 57
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".city-chip\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  55 |     const resp = await ctx.get(BASE_URL);\n  56 |     const html = await resp.text();\n> 57 |     expect(html).toContain('.city-chip');\n     |                  ^\n  58 |     expect(html).toContain('.city-chip-del');\n  59 |     expect(html).toContain('.city-chips-wrap');\n  60 |   });\n    at /var/www/360lm/tests/admin.spec.js:57:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:45:44.714Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structure-city-chip-CSS-classes-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 57
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-08a8955bfdf74caae94b",
              "file": "admin.spec.js",
              "line": 53,
              "column": 3
            },
            {
              "title": "existing screens still present (no regression)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1019,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:66:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 66
                        },
                        "snippet": "  64 |     const resp = await ctx.get(BASE_URL);\n  65 |     const html = await resp.text();\n> 66 |     expect(html).toContain('id=\"scr-companies\"');\n     |                  ^\n  67 |     expect(html).toContain('id=\"scr-employees\"');\n  68 |     expect(html).toContain('id=\"scr-access\"');\n  69 |     expect(html).toContain('id=\"scr-pwa-registry\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 66
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  64 |     const resp = await ctx.get(BASE_URL);\n  65 |     const html = await resp.text();\n> 66 |     expect(html).toContain('id=\"scr-companies\"');\n     |                  ^\n  67 |     expect(html).toContain('id=\"scr-employees\"');\n  68 |     expect(html).toContain('id=\"scr-access\"');\n  69 |     expect(html).toContain('id=\"scr-pwa-registry\"');\n    at /var/www/360lm/tests/admin.spec.js:66:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:45.568Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-cc0ca-till-present-no-regression--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 66
                      }
                    },
                    {
                      "workerIndex": 1020,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:66:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 66
                        },
                        "snippet": "  64 |     const resp = await ctx.get(BASE_URL);\n  65 |     const html = await resp.text();\n> 66 |     expect(html).toContain('id=\"scr-companies\"');\n     |                  ^\n  67 |     expect(html).toContain('id=\"scr-employees\"');\n  68 |     expect(html).toContain('id=\"scr-access\"');\n  69 |     expect(html).toContain('id=\"scr-pwa-registry\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 66
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-companies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  64 |     const resp = await ctx.get(BASE_URL);\n  65 |     const html = await resp.text();\n> 66 |     expect(html).toContain('id=\"scr-companies\"');\n     |                  ^\n  67 |     expect(html).toContain('id=\"scr-employees\"');\n  68 |     expect(html).toContain('id=\"scr-access\"');\n  69 |     expect(html).toContain('id=\"scr-pwa-registry\"');\n    at /var/www/360lm/tests/admin.spec.js:66:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:45:46.430Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-HTML-Structu-cc0ca-till-present-no-regression--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 66
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-86c0df6789935e8a4935",
              "file": "admin.spec.js",
              "line": 62,
              "column": 3
            }
          ]
        },
        {
          "title": "Admin — Client Zone CRUD",
          "file": "admin.spec.js",
          "line": 75,
          "column": 6,
          "specs": [
            {
              "title": "source contains all zone management functions",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 14,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 85,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:80:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 80
                        },
                        "snippet": "  78 |     const resp = await ctx.get(BASE_URL);\n  79 |     const src = await resp.text();\n> 80 |     expect(src).toContain('showZones');\n     |                 ^\n  81 |     expect(src).toContain('refreshZones');\n  82 |     expect(src).toContain('openZoneModal');\n  83 |     expect(src).toContain('saveZone');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 80
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  78 |     const resp = await ctx.get(BASE_URL);\n  79 |     const src = await resp.text();\n> 80 |     expect(src).toContain('showZones');\n     |                 ^\n  81 |     expect(src).toContain('refreshZones');\n  82 |     expect(src).toContain('openZoneModal');\n  83 |     expect(src).toContain('saveZone');\n    at /var/www/360lm/tests/admin.spec.js:80:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:03.131Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--8a949-l-zone-management-functions-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 80
                      }
                    },
                    {
                      "workerIndex": 15,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 73,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:80:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 80
                        },
                        "snippet": "  78 |     const resp = await ctx.get(BASE_URL);\n  79 |     const src = await resp.text();\n> 80 |     expect(src).toContain('showZones');\n     |                 ^\n  81 |     expect(src).toContain('refreshZones');\n  82 |     expect(src).toContain('openZoneModal');\n  83 |     expect(src).toContain('saveZone');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 80
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  78 |     const resp = await ctx.get(BASE_URL);\n  79 |     const src = await resp.text();\n> 80 |     expect(src).toContain('showZones');\n     |                 ^\n  81 |     expect(src).toContain('refreshZones');\n  82 |     expect(src).toContain('openZoneModal');\n  83 |     expect(src).toContain('saveZone');\n    at /var/www/360lm/tests/admin.spec.js:80:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:04.471Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--8a949-l-zone-management-functions-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 80
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-1bc3dfbd85f92e6fd55d",
              "file": "admin.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "source contains CLI_H and CLI_RD client schema headers",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 16,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 109,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:95:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 95
                        },
                        "snippet": "  93 |     const resp = await ctx.get(BASE_URL);\n  94 |     const src = await resp.text();\n> 95 |     expect(src).toContain('CLI_H');\n     |                 ^\n  96 |     expect(src).toContain('CLI_RD');\n  97 |     expect(src).toContain(\"'Accept-Profile':'client'\");\n  98 |     expect(src).toContain(\"'Content-Profile':'client'\");"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 95
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  93 |     const resp = await ctx.get(BASE_URL);\n  94 |     const src = await resp.text();\n> 95 |     expect(src).toContain('CLI_H');\n     |                 ^\n  96 |     expect(src).toContain('CLI_RD');\n  97 |     expect(src).toContain(\"'Accept-Profile':'client'\");\n  98 |     expect(src).toContain(\"'Content-Profile':'client'\");\n    at /var/www/360lm/tests/admin.spec.js:95:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:05.888Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--18144-LI-RD-client-schema-headers-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 95
                      }
                    },
                    {
                      "workerIndex": 17,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 81,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:95:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 95
                        },
                        "snippet": "  93 |     const resp = await ctx.get(BASE_URL);\n  94 |     const src = await resp.text();\n> 95 |     expect(src).toContain('CLI_H');\n     |                 ^\n  96 |     expect(src).toContain('CLI_RD');\n  97 |     expect(src).toContain(\"'Accept-Profile':'client'\");\n  98 |     expect(src).toContain(\"'Content-Profile':'client'\");"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 95
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  93 |     const resp = await ctx.get(BASE_URL);\n  94 |     const src = await resp.text();\n> 95 |     expect(src).toContain('CLI_H');\n     |                 ^\n  96 |     expect(src).toContain('CLI_RD');\n  97 |     expect(src).toContain(\"'Accept-Profile':'client'\");\n  98 |     expect(src).toContain(\"'Content-Profile':'client'\");\n    at /var/www/360lm/tests/admin.spec.js:95:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:07.048Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--18144-LI-RD-client-schema-headers-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 95
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-74f0c63c37aeb5df8fba",
              "file": "admin.spec.js",
              "line": 91,
              "column": 3
            },
            {
              "title": "delete guard checks client.accounts before deleting zone",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 18,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:105:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 105
                        },
                        "snippet": "  103 |     const resp = await ctx.get(BASE_URL);\n  104 |     const src = await resp.text();\n> 105 |     expect(src).toContain('accounts?zone=eq.');\n      |                 ^\n  106 |     expect(src).toContain('it is assigned to one or more client accounts');\n  107 |   });\n  108 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 105
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  103 |     const resp = await ctx.get(BASE_URL);\n  104 |     const src = await resp.text();\n> 105 |     expect(src).toContain('accounts?zone=eq.');\n      |                 ^\n  106 |     expect(src).toContain('it is assigned to one or more client accounts');\n  107 |   });\n  108 |\n    at /var/www/360lm/tests/admin.spec.js:105:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:08.361Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--de039-counts-before-deleting-zone-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 105
                      }
                    },
                    {
                      "workerIndex": 19,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:105:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 105
                        },
                        "snippet": "  103 |     const resp = await ctx.get(BASE_URL);\n  104 |     const src = await resp.text();\n> 105 |     expect(src).toContain('accounts?zone=eq.');\n      |                 ^\n  106 |     expect(src).toContain('it is assigned to one or more client accounts');\n  107 |   });\n  108 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 105
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  103 |     const resp = await ctx.get(BASE_URL);\n  104 |     const src = await resp.text();\n> 105 |     expect(src).toContain('accounts?zone=eq.');\n      |                 ^\n  106 |     expect(src).toContain('it is assigned to one or more client accounts');\n  107 |   });\n  108 |\n    at /var/www/360lm/tests/admin.spec.js:105:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:09.382Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--de039-counts-before-deleting-zone-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 105
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-ac979f5af01c8aeaf42e",
              "file": "admin.spec.js",
              "line": 101,
              "column": 3
            },
            {
              "title": "zone modal auto-uppercases name input",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 20,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:113:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 113
                        },
                        "snippet": "  111 |     const resp = await ctx.get(BASE_URL);\n  112 |     const src = await resp.text();\n> 113 |     expect(src).toContain('.toUpperCase()');\n      |                 ^\n  114 |     expect(src).toContain('zone-name-input');\n  115 |   });\n  116 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 113
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  111 |     const resp = await ctx.get(BASE_URL);\n  112 |     const src = await resp.text();\n> 113 |     expect(src).toContain('.toUpperCase()');\n      |                 ^\n  114 |     expect(src).toContain('zone-name-input');\n  115 |   });\n  116 |\n    at /var/www/360lm/tests/admin.spec.js:113:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:10.372Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--ddbb9--auto-uppercases-name-input-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 113
                      }
                    },
                    {
                      "workerIndex": 21,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:113:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 113
                        },
                        "snippet": "  111 |     const resp = await ctx.get(BASE_URL);\n  112 |     const src = await resp.text();\n> 113 |     expect(src).toContain('.toUpperCase()');\n      |                 ^\n  114 |     expect(src).toContain('zone-name-input');\n  115 |   });\n  116 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 113
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  111 |     const resp = await ctx.get(BASE_URL);\n  112 |     const src = await resp.text();\n> 113 |     expect(src).toContain('.toUpperCase()');\n      |                 ^\n  114 |     expect(src).toContain('zone-name-input');\n  115 |   });\n  116 |\n    at /var/www/360lm/tests/admin.spec.js:113:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:11.283Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--ddbb9--auto-uppercases-name-input-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 113
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-acbbbbab5bc0ba17577a",
              "file": "admin.spec.js",
              "line": 109,
              "column": 3
            },
            {
              "title": "zone screen is accessible with admin session",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 22,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8263,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:126:45",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 45,
                          "line": 126
                        },
                        "snippet": "  124 |     await page.goto(BASE_URL);\n  125 |     // Should be on home screen\n> 126 |     await expect(page.locator('#scr-home')).toBeVisible({ timeout: 8000 });\n      |                                             ^\n  127 |     // Zones screen should exist in DOM\n  128 |     await expect(page.locator('#scr-zones')).toHaveCount(1);\n  129 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 45,
                            "line": 126
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n\n\n  124 |     await page.goto(BASE_URL);\n  125 |     // Should be on home screen\n> 126 |     await expect(page.locator('#scr-home')).toBeVisible({ timeout: 8000 });\n      |                                             ^\n  127 |     // Zones screen should exist in DOM\n  128 |     await expect(page.locator('#scr-zones')).toHaveCount(1);\n  129 |   });\n    at /var/www/360lm/tests/admin.spec.js:126:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:12.170Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--93d9f-cessible-with-admin-session-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--93d9f-cessible-with-admin-session-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 45,
                        "line": 126
                      }
                    },
                    {
                      "workerIndex": 23,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8265,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:126:45",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 45,
                          "line": 126
                        },
                        "snippet": "  124 |     await page.goto(BASE_URL);\n  125 |     // Should be on home screen\n> 126 |     await expect(page.locator('#scr-home')).toBeVisible({ timeout: 8000 });\n      |                                             ^\n  127 |     // Zones screen should exist in DOM\n  128 |     await expect(page.locator('#scr-zones')).toHaveCount(1);\n  129 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 45,
                            "line": 126
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n\n\n  124 |     await page.goto(BASE_URL);\n  125 |     // Should be on home screen\n> 126 |     await expect(page.locator('#scr-home')).toBeVisible({ timeout: 8000 });\n      |                                             ^\n  127 |     // Zones screen should exist in DOM\n  128 |     await expect(page.locator('#scr-zones')).toHaveCount(1);\n  129 |   });\n    at /var/www/360lm/tests/admin.spec.js:126:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:21.445Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--93d9f-cessible-with-admin-session-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--93d9f-cessible-with-admin-session-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 45,
                        "line": 126
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-34e730fa40627afa7a0c",
              "file": "admin.spec.js",
              "line": 117,
              "column": 3
            },
            {
              "title": "zone modal is hidden by default",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 24,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 4265,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:133:51",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 51,
                          "line": 133
                        },
                        "snippet": "  131 |   test('zone modal is hidden by default', async ({ page }) => {\n  132 |     await page.goto(BASE_URL);\n> 133 |     await expect(page.locator('#modal-zone')).not.toHaveClass(/open/, { timeout: 4000 });\n      |                                                   ^\n  134 |   });\n  135 | });\n  136 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 51,
                            "line": 133
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n\n\n  131 |   test('zone modal is hidden by default', async ({ page }) => {\n  132 |     await page.goto(BASE_URL);\n> 133 |     await expect(page.locator('#modal-zone')).not.toHaveClass(/open/, { timeout: 4000 });\n      |                                                   ^\n  134 |   });\n  135 | });\n  136 |\n    at /var/www/360lm/tests/admin.spec.js:133:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:30.748Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--4e5e1--modal-is-hidden-by-default-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--4e5e1--modal-is-hidden-by-default-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 51,
                        "line": 133
                      }
                    },
                    {
                      "workerIndex": 25,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 4280,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:133:51",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 51,
                          "line": 133
                        },
                        "snippet": "  131 |   test('zone modal is hidden by default', async ({ page }) => {\n  132 |     await page.goto(BASE_URL);\n> 133 |     await expect(page.locator('#modal-zone')).not.toHaveClass(/open/, { timeout: 4000 });\n      |                                                   ^\n  134 |   });\n  135 | });\n  136 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 51,
                            "line": 133
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n\n\n  131 |   test('zone modal is hidden by default', async ({ page }) => {\n  132 |     await page.goto(BASE_URL);\n> 133 |     await expect(page.locator('#modal-zone')).not.toHaveClass(/open/, { timeout: 4000 });\n      |                                                   ^\n  134 |   });\n  135 | });\n  136 |\n    at /var/www/360lm/tests/admin.spec.js:133:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:36.074Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--4e5e1--modal-is-hidden-by-default-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--4e5e1--modal-is-hidden-by-default-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 51,
                        "line": 133
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-dd952bbbce25781a6d6b",
              "file": "admin.spec.js",
              "line": 131,
              "column": 3
            },
            {
              "title": "source contains all zone management functions",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1021,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:80:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 80
                        },
                        "snippet": "  78 |     const resp = await ctx.get(BASE_URL);\n  79 |     const src = await resp.text();\n> 80 |     expect(src).toContain('showZones');\n     |                 ^\n  81 |     expect(src).toContain('refreshZones');\n  82 |     expect(src).toContain('openZoneModal');\n  83 |     expect(src).toContain('saveZone');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 80
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  78 |     const resp = await ctx.get(BASE_URL);\n  79 |     const src = await resp.text();\n> 80 |     expect(src).toContain('showZones');\n     |                 ^\n  81 |     expect(src).toContain('refreshZones');\n  82 |     expect(src).toContain('openZoneModal');\n  83 |     expect(src).toContain('saveZone');\n    at /var/www/360lm/tests/admin.spec.js:80:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:47.294Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--8a949-l-zone-management-functions-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 80
                      }
                    },
                    {
                      "workerIndex": 1022,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:80:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 80
                        },
                        "snippet": "  78 |     const resp = await ctx.get(BASE_URL);\n  79 |     const src = await resp.text();\n> 80 |     expect(src).toContain('showZones');\n     |                 ^\n  81 |     expect(src).toContain('refreshZones');\n  82 |     expect(src).toContain('openZoneModal');\n  83 |     expect(src).toContain('saveZone');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 80
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"showZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  78 |     const resp = await ctx.get(BASE_URL);\n  79 |     const src = await resp.text();\n> 80 |     expect(src).toContain('showZones');\n     |                 ^\n  81 |     expect(src).toContain('refreshZones');\n  82 |     expect(src).toContain('openZoneModal');\n  83 |     expect(src).toContain('saveZone');\n    at /var/www/360lm/tests/admin.spec.js:80:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:45:48.183Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--8a949-l-zone-management-functions-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 80
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-94c8e174f416b1498338",
              "file": "admin.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "source contains CLI_H and CLI_RD client schema headers",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1023,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:95:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 95
                        },
                        "snippet": "  93 |     const resp = await ctx.get(BASE_URL);\n  94 |     const src = await resp.text();\n> 95 |     expect(src).toContain('CLI_H');\n     |                 ^\n  96 |     expect(src).toContain('CLI_RD');\n  97 |     expect(src).toContain(\"'Accept-Profile':'client'\");\n  98 |     expect(src).toContain(\"'Content-Profile':'client'\");"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 95
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  93 |     const resp = await ctx.get(BASE_URL);\n  94 |     const src = await resp.text();\n> 95 |     expect(src).toContain('CLI_H');\n     |                 ^\n  96 |     expect(src).toContain('CLI_RD');\n  97 |     expect(src).toContain(\"'Accept-Profile':'client'\");\n  98 |     expect(src).toContain(\"'Content-Profile':'client'\");\n    at /var/www/360lm/tests/admin.spec.js:95:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:49.122Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--18144-LI-RD-client-schema-headers-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 95
                      }
                    },
                    {
                      "workerIndex": 1024,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:95:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 95
                        },
                        "snippet": "  93 |     const resp = await ctx.get(BASE_URL);\n  94 |     const src = await resp.text();\n> 95 |     expect(src).toContain('CLI_H');\n     |                 ^\n  96 |     expect(src).toContain('CLI_RD');\n  97 |     expect(src).toContain(\"'Accept-Profile':'client'\");\n  98 |     expect(src).toContain(\"'Content-Profile':'client'\");"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 95
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CLI_H\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  93 |     const resp = await ctx.get(BASE_URL);\n  94 |     const src = await resp.text();\n> 95 |     expect(src).toContain('CLI_H');\n     |                 ^\n  96 |     expect(src).toContain('CLI_RD');\n  97 |     expect(src).toContain(\"'Accept-Profile':'client'\");\n  98 |     expect(src).toContain(\"'Content-Profile':'client'\");\n    at /var/www/360lm/tests/admin.spec.js:95:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:45:50.057Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--18144-LI-RD-client-schema-headers-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 95
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-bdf5a9532f810a2c50b7",
              "file": "admin.spec.js",
              "line": 91,
              "column": 3
            },
            {
              "title": "delete guard checks client.accounts before deleting zone",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1025,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:105:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 105
                        },
                        "snippet": "  103 |     const resp = await ctx.get(BASE_URL);\n  104 |     const src = await resp.text();\n> 105 |     expect(src).toContain('accounts?zone=eq.');\n      |                 ^\n  106 |     expect(src).toContain('it is assigned to one or more client accounts');\n  107 |   });\n  108 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 105
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  103 |     const resp = await ctx.get(BASE_URL);\n  104 |     const src = await resp.text();\n> 105 |     expect(src).toContain('accounts?zone=eq.');\n      |                 ^\n  106 |     expect(src).toContain('it is assigned to one or more client accounts');\n  107 |   });\n  108 |\n    at /var/www/360lm/tests/admin.spec.js:105:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:50.929Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--de039-counts-before-deleting-zone-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 105
                      }
                    },
                    {
                      "workerIndex": 1026,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:105:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 105
                        },
                        "snippet": "  103 |     const resp = await ctx.get(BASE_URL);\n  104 |     const src = await resp.text();\n> 105 |     expect(src).toContain('accounts?zone=eq.');\n      |                 ^\n  106 |     expect(src).toContain('it is assigned to one or more client accounts');\n  107 |   });\n  108 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 105
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"accounts?zone=eq.\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  103 |     const resp = await ctx.get(BASE_URL);\n  104 |     const src = await resp.text();\n> 105 |     expect(src).toContain('accounts?zone=eq.');\n      |                 ^\n  106 |     expect(src).toContain('it is assigned to one or more client accounts');\n  107 |   });\n  108 |\n    at /var/www/360lm/tests/admin.spec.js:105:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:45:51.792Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--de039-counts-before-deleting-zone-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 105
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-3e89a03bd0eb9f866797",
              "file": "admin.spec.js",
              "line": 101,
              "column": 3
            },
            {
              "title": "zone modal auto-uppercases name input",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1027,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:113:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 113
                        },
                        "snippet": "  111 |     const resp = await ctx.get(BASE_URL);\n  112 |     const src = await resp.text();\n> 113 |     expect(src).toContain('.toUpperCase()');\n      |                 ^\n  114 |     expect(src).toContain('zone-name-input');\n  115 |   });\n  116 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 113
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  111 |     const resp = await ctx.get(BASE_URL);\n  112 |     const src = await resp.text();\n> 113 |     expect(src).toContain('.toUpperCase()');\n      |                 ^\n  114 |     expect(src).toContain('zone-name-input');\n  115 |   });\n  116 |\n    at /var/www/360lm/tests/admin.spec.js:113:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:52.706Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--ddbb9--auto-uppercases-name-input-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 113
                      }
                    },
                    {
                      "workerIndex": 1028,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:113:17",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 17,
                          "line": 113
                        },
                        "snippet": "  111 |     const resp = await ctx.get(BASE_URL);\n  112 |     const src = await resp.text();\n> 113 |     expect(src).toContain('.toUpperCase()');\n      |                 ^\n  114 |     expect(src).toContain('zone-name-input');\n  115 |   });\n  116 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 17,
                            "line": 113
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".toUpperCase()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  111 |     const resp = await ctx.get(BASE_URL);\n  112 |     const src = await resp.text();\n> 113 |     expect(src).toContain('.toUpperCase()');\n      |                 ^\n  114 |     expect(src).toContain('zone-name-input');\n  115 |   });\n  116 |\n    at /var/www/360lm/tests/admin.spec.js:113:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:45:53.538Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--ddbb9--auto-uppercases-name-input-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 17,
                        "line": 113
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-a9a4e7e7f70d2ff060d7",
              "file": "admin.spec.js",
              "line": 109,
              "column": 3
            },
            {
              "title": "zone screen is accessible with admin session",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1029,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8281,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:126:45",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 45,
                          "line": 126
                        },
                        "snippet": "  124 |     await page.goto(BASE_URL);\n  125 |     // Should be on home screen\n> 126 |     await expect(page.locator('#scr-home')).toBeVisible({ timeout: 8000 });\n      |                                             ^\n  127 |     // Zones screen should exist in DOM\n  128 |     await expect(page.locator('#scr-zones')).toHaveCount(1);\n  129 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 45,
                            "line": 126
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n\n\n  124 |     await page.goto(BASE_URL);\n  125 |     // Should be on home screen\n> 126 |     await expect(page.locator('#scr-home')).toBeVisible({ timeout: 8000 });\n      |                                             ^\n  127 |     // Zones screen should exist in DOM\n  128 |     await expect(page.locator('#scr-zones')).toHaveCount(1);\n  129 |   });\n    at /var/www/360lm/tests/admin.spec.js:126:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:45:54.437Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--93d9f-cessible-with-admin-session-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--93d9f-cessible-with-admin-session-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 45,
                        "line": 126
                      }
                    },
                    {
                      "workerIndex": 1030,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8297,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:126:45",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 45,
                          "line": 126
                        },
                        "snippet": "  124 |     await page.goto(BASE_URL);\n  125 |     // Should be on home screen\n> 126 |     await expect(page.locator('#scr-home')).toBeVisible({ timeout: 8000 });\n      |                                             ^\n  127 |     // Zones screen should exist in DOM\n  128 |     await expect(page.locator('#scr-zones')).toHaveCount(1);\n  129 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 45,
                            "line": 126
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home')\u001b[22m\n\n\n  124 |     await page.goto(BASE_URL);\n  125 |     // Should be on home screen\n> 126 |     await expect(page.locator('#scr-home')).toBeVisible({ timeout: 8000 });\n      |                                             ^\n  127 |     // Zones screen should exist in DOM\n  128 |     await expect(page.locator('#scr-zones')).toHaveCount(1);\n  129 |   });\n    at /var/www/360lm/tests/admin.spec.js:126:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:46:04.088Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--93d9f-cessible-with-admin-session-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--93d9f-cessible-with-admin-session-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 45,
                        "line": 126
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-14e9a21562a239507696",
              "file": "admin.spec.js",
              "line": 117,
              "column": 3
            },
            {
              "title": "zone modal is hidden by default",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1031,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 4238,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:133:51",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 51,
                          "line": 133
                        },
                        "snippet": "  131 |   test('zone modal is hidden by default', async ({ page }) => {\n  132 |     await page.goto(BASE_URL);\n> 133 |     await expect(page.locator('#modal-zone')).not.toHaveClass(/open/, { timeout: 4000 });\n      |                                                   ^\n  134 |   });\n  135 | });\n  136 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 51,
                            "line": 133
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n\n\n  131 |   test('zone modal is hidden by default', async ({ page }) => {\n  132 |     await page.goto(BASE_URL);\n> 133 |     await expect(page.locator('#modal-zone')).not.toHaveClass(/open/, { timeout: 4000 });\n      |                                                   ^\n  134 |   });\n  135 | });\n  136 |\n    at /var/www/360lm/tests/admin.spec.js:133:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:46:13.447Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--4e5e1--modal-is-hidden-by-default-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--4e5e1--modal-is-hidden-by-default-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 51,
                        "line": 133
                      }
                    },
                    {
                      "workerIndex": 1032,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 4239,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n\n    at /var/www/360lm/tests/admin.spec.js:133:51",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 51,
                          "line": 133
                        },
                        "snippet": "  131 |   test('zone modal is hidden by default', async ({ page }) => {\n  132 |     await page.goto(BASE_URL);\n> 133 |     await expect(page.locator('#modal-zone')).not.toHaveClass(/open/, { timeout: 4000 });\n      |                                                   ^\n  134 |   });\n  135 | });\n  136 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 51,
                            "line": 133
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#modal-zone')\nExpected pattern: not \u001b[32m/open/\u001b[39m\nTimeout: 4000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveClass\" with timeout 4000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-zone')\u001b[22m\n\n\n  131 |   test('zone modal is hidden by default', async ({ page }) => {\n  132 |     await page.goto(BASE_URL);\n> 133 |     await expect(page.locator('#modal-zone')).not.toHaveClass(/open/, { timeout: 4000 });\n      |                                                   ^\n  134 |   });\n  135 | });\n  136 |\n    at /var/www/360lm/tests/admin.spec.js:133:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:46:18.666Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--4e5e1--modal-is-hidden-by-default-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Client-Zone--4e5e1--modal-is-hidden-by-default-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 51,
                        "line": 133
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-0479c0e5b7dc281d56ca",
              "file": "admin.spec.js",
              "line": 131,
              "column": 3
            }
          ]
        },
        {
          "title": "Admin — DB Schema (Step 1 migrations)",
          "file": "admin.spec.js",
          "line": 138,
          "column": 6,
          "specs": [
            {
              "title": "client.zones table exists and is accessible via PostgREST",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 26,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 307,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:144:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 144
                        },
                        "snippet": "  142 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  143 |     );\n> 144 |     expect(resp.status()).toBe(200);\n      |                           ^\n  145 |     const body = await resp.json();\n  146 |     expect(Array.isArray(body)).toBe(true);\n  147 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 144
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n\n  142 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  143 |     );\n> 144 |     expect(resp.status()).toBe(200);\n      |                           ^\n  145 |     const body = await resp.json();\n  146 |     expect(Array.isArray(body)).toBe(true);\n  147 |   });\n    at /var/www/360lm/tests/admin.spec.js:144:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:41.510Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-c9002-is-accessible-via-PostgREST-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-c9002-is-accessible-via-PostgREST-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 144
                      }
                    },
                    {
                      "workerIndex": 27,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 285,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:144:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 144
                        },
                        "snippet": "  142 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  143 |     );\n> 144 |     expect(resp.status()).toBe(200);\n      |                           ^\n  145 |     const body = await resp.json();\n  146 |     expect(Array.isArray(body)).toBe(true);\n  147 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 144
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n\n  142 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  143 |     );\n> 144 |     expect(resp.status()).toBe(200);\n      |                           ^\n  145 |     const body = await resp.json();\n  146 |     expect(Array.isArray(body)).toBe(true);\n  147 |   });\n    at /var/www/360lm/tests/admin.spec.js:144:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:42.882Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-c9002-is-accessible-via-PostgREST-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-c9002-is-accessible-via-PostgREST-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 144
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-e2d62a2dca79a6c6de1f",
              "file": "admin.spec.js",
              "line": 139,
              "column": 3
            },
            {
              "title": "recce.branding_approvals table exists and is accessible",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 28,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 277,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:44.141Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-a31533459baac9c54098",
              "file": "admin.spec.js",
              "line": 149,
              "column": 3
            },
            {
              "title": "client.accounts has zone and is_head columns",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 28,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 184,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:164:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 164
                        },
                        "snippet": "  162 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  163 |     );\n> 164 |     expect(resp.status()).toBe(200);\n      |                           ^\n  165 |   });\n  166 |\n  167 |   test('sales.jobs has zones column (ADR-111: sales.jobs is canonical)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 164
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n\n  162 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  163 |     );\n> 164 |     expect(resp.status()).toBe(200);\n      |                           ^\n  165 |   });\n  166 |\n  167 |   test('sales.jobs has zones column (ADR-111: sales.jobs is canonical)', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:164:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:44.579Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-b1423-as-zone-and-is-head-columns-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-b1423-as-zone-and-is-head-columns-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 164
                      }
                    },
                    {
                      "workerIndex": 29,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 303,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:164:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 164
                        },
                        "snippet": "  162 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  163 |     );\n> 164 |     expect(resp.status()).toBe(200);\n      |                           ^\n  165 |   });\n  166 |\n  167 |   test('sales.jobs has zones column (ADR-111: sales.jobs is canonical)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 164
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n\n  162 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  163 |     );\n> 164 |     expect(resp.status()).toBe(200);\n      |                           ^\n  165 |   });\n  166 |\n  167 |   test('sales.jobs has zones column (ADR-111: sales.jobs is canonical)', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:164:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:45.613Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-b1423-as-zone-and-is-head-columns-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-b1423-as-zone-and-is-head-columns-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 164
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-90b160b77ed82210f0c8",
              "file": "admin.spec.js",
              "line": 159,
              "column": 3
            },
            {
              "title": "sales.jobs has zones column (ADR-111: sales.jobs is canonical)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 30,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 268,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:172:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 172
                        },
                        "snippet": "  170 |       { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } }\n  171 |     );\n> 172 |     expect(resp.status()).toBe(200);\n      |                           ^\n  173 |   });\n  174 |\n  175 |   test('sales.jobs has campaign_id column', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 172
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m\n\n  170 |       { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } }\n  171 |     );\n> 172 |     expect(resp.status()).toBe(200);\n      |                           ^\n  173 |   });\n  174 |\n  175 |   test('sales.jobs has campaign_id column', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:172:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:46.834Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-92703-11-sales-jobs-is-canonical--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-92703-11-sales-jobs-is-canonical--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 172
                      }
                    },
                    {
                      "workerIndex": 31,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 320,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:172:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 172
                        },
                        "snippet": "  170 |       { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } }\n  171 |     );\n> 172 |     expect(resp.status()).toBe(200);\n      |                           ^\n  173 |   });\n  174 |\n  175 |   test('sales.jobs has campaign_id column', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 172
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m\n\n  170 |       { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } }\n  171 |     );\n> 172 |     expect(resp.status()).toBe(200);\n      |                           ^\n  173 |   });\n  174 |\n  175 |   test('sales.jobs has campaign_id column', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:172:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:48.061Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-92703-11-sales-jobs-is-canonical--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-92703-11-sales-jobs-is-canonical--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 172
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-8e8ae24e2069919600be",
              "file": "admin.spec.js",
              "line": 167,
              "column": 3
            },
            {
              "title": "sales.jobs has campaign_id column",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 32,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 257,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:49.527Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-5cb55da05cf1ea6843f2",
              "file": "admin.spec.js",
              "line": 175,
              "column": 3
            },
            {
              "title": "client.zones table exists and is accessible via PostgREST",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1033,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 265,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:144:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 144
                        },
                        "snippet": "  142 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  143 |     );\n> 144 |     expect(resp.status()).toBe(200);\n      |                           ^\n  145 |     const body = await resp.json();\n  146 |     expect(Array.isArray(body)).toBe(true);\n  147 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 144
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n\n  142 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  143 |     );\n> 144 |     expect(resp.status()).toBe(200);\n      |                           ^\n  145 |     const body = await resp.json();\n  146 |     expect(Array.isArray(body)).toBe(true);\n  147 |   });\n    at /var/www/360lm/tests/admin.spec.js:144:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:46:23.867Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-c9002-is-accessible-via-PostgREST-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-c9002-is-accessible-via-PostgREST-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 144
                      }
                    },
                    {
                      "workerIndex": 1034,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 269,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:144:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 144
                        },
                        "snippet": "  142 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  143 |     );\n> 144 |     expect(resp.status()).toBe(200);\n      |                           ^\n  145 |     const body = await resp.json();\n  146 |     expect(Array.isArray(body)).toBe(true);\n  147 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 144
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n\n  142 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  143 |     );\n> 144 |     expect(resp.status()).toBe(200);\n      |                           ^\n  145 |     const body = await resp.json();\n  146 |     expect(Array.isArray(body)).toBe(true);\n  147 |   });\n    at /var/www/360lm/tests/admin.spec.js:144:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:46:25.141Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-c9002-is-accessible-via-PostgREST-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-c9002-is-accessible-via-PostgREST-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 144
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-b14753baa812af641406",
              "file": "admin.spec.js",
              "line": 139,
              "column": 3
            },
            {
              "title": "recce.branding_approvals table exists and is accessible",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1035,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 300,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:46:26.433Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-119c143c053c4e3b7dab",
              "file": "admin.spec.js",
              "line": 149,
              "column": 3
            },
            {
              "title": "client.accounts has zone and is_head columns",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1035,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 161,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:164:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 164
                        },
                        "snippet": "  162 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  163 |     );\n> 164 |     expect(resp.status()).toBe(200);\n      |                           ^\n  165 |   });\n  166 |\n  167 |   test('sales.jobs has zones column (ADR-111: sales.jobs is canonical)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 164
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n\n  162 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  163 |     );\n> 164 |     expect(resp.status()).toBe(200);\n      |                           ^\n  165 |   });\n  166 |\n  167 |   test('sales.jobs has zones column (ADR-111: sales.jobs is canonical)', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:164:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:46:26.889Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-b1423-as-zone-and-is-head-columns-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-b1423-as-zone-and-is-head-columns-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 164
                      }
                    },
                    {
                      "workerIndex": 1036,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 293,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:164:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 164
                        },
                        "snippet": "  162 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  163 |     );\n> 164 |     expect(resp.status()).toBe(200);\n      |                           ^\n  165 |   });\n  166 |\n  167 |   test('sales.jobs has zones column (ADR-111: sales.jobs is canonical)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 164
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m406\u001b[39m\n\n  162 |       { headers: { 'Accept-Profile': 'client', 'Accept': 'application/json' } }\n  163 |     );\n> 164 |     expect(resp.status()).toBe(200);\n      |                           ^\n  165 |   });\n  166 |\n  167 |   test('sales.jobs has zones column (ADR-111: sales.jobs is canonical)', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:164:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:46:27.929Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-b1423-as-zone-and-is-head-columns-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-b1423-as-zone-and-is-head-columns-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 164
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-594eb74c1095c9886f73",
              "file": "admin.spec.js",
              "line": 159,
              "column": 3
            },
            {
              "title": "sales.jobs has zones column (ADR-111: sales.jobs is canonical)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1037,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 319,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:172:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 172
                        },
                        "snippet": "  170 |       { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } }\n  171 |     );\n> 172 |     expect(resp.status()).toBe(200);\n      |                           ^\n  173 |   });\n  174 |\n  175 |   test('sales.jobs has campaign_id column', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 172
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m\n\n  170 |       { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } }\n  171 |     );\n> 172 |     expect(resp.status()).toBe(200);\n      |                           ^\n  173 |   });\n  174 |\n  175 |   test('sales.jobs has campaign_id column', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:172:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:46:29.341Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-92703-11-sales-jobs-is-canonical--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-92703-11-sales-jobs-is-canonical--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 172
                      }
                    },
                    {
                      "workerIndex": 1038,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 304,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:172:27",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 27,
                          "line": 172
                        },
                        "snippet": "  170 |       { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } }\n  171 |     );\n> 172 |     expect(resp.status()).toBe(200);\n      |                           ^\n  173 |   });\n  174 |\n  175 |   test('sales.jobs has campaign_id column', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 27,
                            "line": 172
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m400\u001b[39m\n\n  170 |       { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } }\n  171 |     );\n> 172 |     expect(resp.status()).toBe(200);\n      |                           ^\n  173 |   });\n  174 |\n  175 |   test('sales.jobs has campaign_id column', async ({ page }) => {\n    at /var/www/360lm/tests/admin.spec.js:172:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:46:30.723Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-92703-11-sales-jobs-is-canonical--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-DB-Schema-St-92703-11-sales-jobs-is-canonical--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 27,
                        "line": 172
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-0f709b268ad1180a5966",
              "file": "admin.spec.js",
              "line": 167,
              "column": 3
            },
            {
              "title": "sales.jobs has campaign_id column",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1039,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 237,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:46:32.077Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "73482573f1568b679d78-1fcbdc35e2babd92dfe8",
              "file": "admin.spec.js",
              "line": 175,
              "column": 3
            }
          ]
        },
        {
          "title": "Admin — Hub Navigation",
          "file": "admin.spec.js",
          "line": 184,
          "column": 6,
          "specs": [
            {
              "title": "home topbar has Hub button (not just text link)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 32,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 42,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:189:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 189
                        },
                        "snippet": "  187 |     const html = await (await ctx.get(BASE_URL)).text();\n  188 |     await ctx.dispose();\n> 189 |     expect(html).toContain(\"window.location.href='/hub/'\");\n      |                  ^\n  190 |     expect(html).toContain('🏡 Hub');\n  191 |   });\n  192 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 189
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  187 |     const html = await (await ctx.get(BASE_URL)).text();\n  188 |     await ctx.dispose();\n> 189 |     expect(html).toContain(\"window.location.href='/hub/'\");\n      |                  ^\n  190 |     expect(html).toContain('🏡 Hub');\n  191 |   });\n  192 | });\n    at /var/www/360lm/tests/admin.spec.js:189:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:49.987Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Hub-Navigati-55a80--button-not-just-text-link--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 189
                      }
                    },
                    {
                      "workerIndex": 33,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 68,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:189:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 189
                        },
                        "snippet": "  187 |     const html = await (await ctx.get(BASE_URL)).text();\n  188 |     await ctx.dispose();\n> 189 |     expect(html).toContain(\"window.location.href='/hub/'\");\n      |                  ^\n  190 |     expect(html).toContain('🏡 Hub');\n  191 |   });\n  192 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 189
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  187 |     const html = await (await ctx.get(BASE_URL)).text();\n  188 |     await ctx.dispose();\n> 189 |     expect(html).toContain(\"window.location.href='/hub/'\");\n      |                  ^\n  190 |     expect(html).toContain('🏡 Hub');\n  191 |   });\n  192 | });\n    at /var/www/360lm/tests/admin.spec.js:189:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:50.860Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Hub-Navigati-55a80--button-not-just-text-link--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 189
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-c75f84faefaaba91a5fe",
              "file": "admin.spec.js",
              "line": 185,
              "column": 3
            },
            {
              "title": "home topbar has Hub button (not just text link)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1039,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 20,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:189:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 189
                        },
                        "snippet": "  187 |     const html = await (await ctx.get(BASE_URL)).text();\n  188 |     await ctx.dispose();\n> 189 |     expect(html).toContain(\"window.location.href='/hub/'\");\n      |                  ^\n  190 |     expect(html).toContain('🏡 Hub');\n  191 |   });\n  192 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 189
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  187 |     const html = await (await ctx.get(BASE_URL)).text();\n  188 |     await ctx.dispose();\n> 189 |     expect(html).toContain(\"window.location.href='/hub/'\");\n      |                  ^\n  190 |     expect(html).toContain('🏡 Hub');\n  191 |   });\n  192 | });\n    at /var/www/360lm/tests/admin.spec.js:189:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:46:32.492Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Hub-Navigati-55a80--button-not-just-text-link--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 189
                      }
                    },
                    {
                      "workerIndex": 1040,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/admin.spec.js:189:18",
                        "location": {
                          "file": "/var/www/360lm/tests/admin.spec.js",
                          "column": 18,
                          "line": 189
                        },
                        "snippet": "  187 |     const html = await (await ctx.get(BASE_URL)).text();\n  188 |     await ctx.dispose();\n> 189 |     expect(html).toContain(\"window.location.href='/hub/'\");\n      |                  ^\n  190 |     expect(html).toContain('🏡 Hub');\n  191 |   });\n  192 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/admin.spec.js",
                            "column": 18,
                            "line": 189
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  187 |     const html = await (await ctx.get(BASE_URL)).text();\n  188 |     await ctx.dispose();\n> 189 |     expect(html).toContain(\"window.location.href='/hub/'\");\n      |                  ^\n  190 |     expect(html).toContain('🏡 Hub');\n  191 |   });\n  192 | });\n    at /var/www/360lm/tests/admin.spec.js:189:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:46:33.303Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/admin-Admin-—-Hub-Navigati-55a80--button-not-just-text-link--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/admin.spec.js",
                        "column": 18,
                        "line": 189
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "73482573f1568b679d78-b7ab3e776b48b5ec2be0",
              "file": "admin.spec.js",
              "line": 185,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "btl.spec.js",
      "file": "btl.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "BTL Offers — Happy Path",
          "file": "btl.spec.js",
          "line": 23,
          "column": 6,
          "specs": [
            {
              "title": "login page renders",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 34,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5336,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n    at /var/www/360lm/tests/btl.spec.js:26:24",
                        "location": {
                          "file": "/var/www/360lm/tests/btl.spec.js",
                          "column": 24,
                          "line": 26
                        },
                        "snippet": "  24 |   test('login page renders', async ({ page }) => {\n  25 |     await page.goto(BASE_URL);\n> 26 |     await expect(page).toHaveTitle(/BTL Offers/);\n     |                        ^\n  27 |     await expect(page.locator('#lg-id')).toBeVisible();\n  28 |     await expect(page.locator('#lg-pin')).toBeVisible();\n  29 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 24,
                            "line": 26
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n\n  24 |   test('login page renders', async ({ page }) => {\n  25 |     await page.goto(BASE_URL);\n> 26 |     await expect(page).toHaveTitle(/BTL Offers/);\n     |                        ^\n  27 |     await expect(page.locator('#lg-id')).toBeVisible();\n  28 |     await expect(page.locator('#lg-pin')).toBeVisible();\n  29 |   });\n    at /var/www/360lm/tests/btl.spec.js:26:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:46:51.779Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-login-page-renders-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-login-page-renders-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/btl.spec.js",
                        "column": 24,
                        "line": 26
                      }
                    },
                    {
                      "workerIndex": 35,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5291,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n    at /var/www/360lm/tests/btl.spec.js:26:24",
                        "location": {
                          "file": "/var/www/360lm/tests/btl.spec.js",
                          "column": 24,
                          "line": 26
                        },
                        "snippet": "  24 |   test('login page renders', async ({ page }) => {\n  25 |     await page.goto(BASE_URL);\n> 26 |     await expect(page).toHaveTitle(/BTL Offers/);\n     |                        ^\n  27 |     await expect(page.locator('#lg-id')).toBeVisible();\n  28 |     await expect(page.locator('#lg-pin')).toBeVisible();\n  29 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 24,
                            "line": 26
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n\n  24 |   test('login page renders', async ({ page }) => {\n  25 |     await page.goto(BASE_URL);\n> 26 |     await expect(page).toHaveTitle(/BTL Offers/);\n     |                        ^\n  27 |     await expect(page.locator('#lg-id')).toBeVisible();\n  28 |     await expect(page.locator('#lg-pin')).toBeVisible();\n  29 |   });\n    at /var/www/360lm/tests/btl.spec.js:26:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:46:58.240Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-login-page-renders-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-login-page-renders-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/btl.spec.js",
                        "column": 24,
                        "line": 26
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-9a7375de6f981e291455",
              "file": "btl.spec.js",
              "line": 24,
              "column": 3
            },
            {
              "title": "CACHE_VER in source matches spec",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 36,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 67,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/btl.spec.js:33:18",
                        "location": {
                          "file": "/var/www/360lm/tests/btl.spec.js",
                          "column": 18,
                          "line": 33
                        },
                        "snippet": "  31 |   test('CACHE_VER in source matches spec', async ({ request }) => {\n  32 |     const html = await (await request.get(BASE_URL)).text();\n> 33 |     expect(html).toContain(`CACHE_VER = '${SW_VER}'`);\n     |                  ^\n  34 |   });\n  35 |\n  36 |   test('partner login lands on My Claims with campaign name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 18,
                            "line": 33
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  31 |   test('CACHE_VER in source matches spec', async ({ request }) => {\n  32 |     const html = await (await request.get(BASE_URL)).text();\n> 33 |     expect(html).toContain(`CACHE_VER = '${SW_VER}'`);\n     |                  ^\n  34 |   });\n  35 |\n  36 |   test('partner login lands on My Claims with campaign name', async ({ page }) => {\n    at /var/www/360lm/tests/btl.spec.js:33:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:47:04.979Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-CACHE-VER-in-source-matches-spec-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/btl.spec.js",
                        "column": 18,
                        "line": 33
                      }
                    },
                    {
                      "workerIndex": 37,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/btl.spec.js:33:18",
                        "location": {
                          "file": "/var/www/360lm/tests/btl.spec.js",
                          "column": 18,
                          "line": 33
                        },
                        "snippet": "  31 |   test('CACHE_VER in source matches spec', async ({ request }) => {\n  32 |     const html = await (await request.get(BASE_URL)).text();\n> 33 |     expect(html).toContain(`CACHE_VER = '${SW_VER}'`);\n     |                  ^\n  34 |   });\n  35 |\n  36 |   test('partner login lands on My Claims with campaign name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 18,
                            "line": 33
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  31 |   test('CACHE_VER in source matches spec', async ({ request }) => {\n  32 |     const html = await (await request.get(BASE_URL)).text();\n> 33 |     expect(html).toContain(`CACHE_VER = '${SW_VER}'`);\n     |                  ^\n  34 |   });\n  35 |\n  36 |   test('partner login lands on My Claims with campaign name', async ({ page }) => {\n    at /var/www/360lm/tests/btl.spec.js:33:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:47:06.100Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-CACHE-VER-in-source-matches-spec-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/btl.spec.js",
                        "column": 18,
                        "line": 33
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-41dacb07b2291eb44d5a",
              "file": "btl.spec.js",
              "line": 31,
              "column": 3
            },
            {
              "title": "partner login lands on My Claims with campaign name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 38,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:37:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:47:07.104Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-337a1-y-Claims-with-campaign-name-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-337a1-y-Claims-with-campaign-name-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 39,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30104,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:37:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:47:38.203Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-337a1-y-Claims-with-campaign-name-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-337a1-y-Claims-with-campaign-name-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-195e17ba3fddb446d1e6",
              "file": "btl.spec.js",
              "line": 36,
              "column": 3
            },
            {
              "title": "admin login lands on queue with campaign filter chips",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 40,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:44:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:48:09.364Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-8b7a7--with-campaign-filter-chips-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-8b7a7--with-campaign-filter-chips-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 41,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30102,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:44:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:48:40.381Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-8b7a7--with-campaign-filter-chips-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-8b7a7--with-campaign-filter-chips-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-0d16fa60e2d8711bbb1a",
              "file": "btl.spec.js",
              "line": 43,
              "column": 3
            },
            {
              "title": "login page renders",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1041,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5305,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n    at /var/www/360lm/tests/btl.spec.js:26:24",
                        "location": {
                          "file": "/var/www/360lm/tests/btl.spec.js",
                          "column": 24,
                          "line": 26
                        },
                        "snippet": "  24 |   test('login page renders', async ({ page }) => {\n  25 |     await page.goto(BASE_URL);\n> 26 |     await expect(page).toHaveTitle(/BTL Offers/);\n     |                        ^\n  27 |     await expect(page.locator('#lg-id')).toBeVisible();\n  28 |     await expect(page.locator('#lg-pin')).toBeVisible();\n  29 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 24,
                            "line": 26
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n\n  24 |   test('login page renders', async ({ page }) => {\n  25 |     await page.goto(BASE_URL);\n> 26 |     await expect(page).toHaveTitle(/BTL Offers/);\n     |                        ^\n  27 |     await expect(page.locator('#lg-id')).toBeVisible();\n  28 |     await expect(page.locator('#lg-pin')).toBeVisible();\n  29 |   });\n    at /var/www/360lm/tests/btl.spec.js:26:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:46:34.170Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-login-page-renders-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-login-page-renders-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/btl.spec.js",
                        "column": 24,
                        "line": 26
                      }
                    },
                    {
                      "workerIndex": 1042,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5241,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n    at /var/www/360lm/tests/btl.spec.js:26:24",
                        "location": {
                          "file": "/var/www/360lm/tests/btl.spec.js",
                          "column": 24,
                          "line": 26
                        },
                        "snippet": "  24 |   test('login page renders', async ({ page }) => {\n  25 |     await page.goto(BASE_URL);\n> 26 |     await expect(page).toHaveTitle(/BTL Offers/);\n     |                        ^\n  27 |     await expect(page.locator('#lg-id')).toBeVisible();\n  28 |     await expect(page.locator('#lg-pin')).toBeVisible();\n  29 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 24,
                            "line": 26
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/BTL Offers/\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n\n  24 |   test('login page renders', async ({ page }) => {\n  25 |     await page.goto(BASE_URL);\n> 26 |     await expect(page).toHaveTitle(/BTL Offers/);\n     |                        ^\n  27 |     await expect(page.locator('#lg-id')).toBeVisible();\n  28 |     await expect(page.locator('#lg-pin')).toBeVisible();\n  29 |   });\n    at /var/www/360lm/tests/btl.spec.js:26:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:46:40.435Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-login-page-renders-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-login-page-renders-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/btl.spec.js",
                        "column": 24,
                        "line": 26
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-3d04922c62f0e22d90d4",
              "file": "btl.spec.js",
              "line": 24,
              "column": 3
            },
            {
              "title": "CACHE_VER in source matches spec",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1043,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/btl.spec.js:33:18",
                        "location": {
                          "file": "/var/www/360lm/tests/btl.spec.js",
                          "column": 18,
                          "line": 33
                        },
                        "snippet": "  31 |   test('CACHE_VER in source matches spec', async ({ request }) => {\n  32 |     const html = await (await request.get(BASE_URL)).text();\n> 33 |     expect(html).toContain(`CACHE_VER = '${SW_VER}'`);\n     |                  ^\n  34 |   });\n  35 |\n  36 |   test('partner login lands on My Claims with campaign name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 18,
                            "line": 33
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  31 |   test('CACHE_VER in source matches spec', async ({ request }) => {\n  32 |     const html = await (await request.get(BASE_URL)).text();\n> 33 |     expect(html).toContain(`CACHE_VER = '${SW_VER}'`);\n     |                  ^\n  34 |   });\n  35 |\n  36 |   test('partner login lands on My Claims with campaign name', async ({ page }) => {\n    at /var/www/360lm/tests/btl.spec.js:33:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:46:46.637Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-CACHE-VER-in-source-matches-spec-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/btl.spec.js",
                        "column": 18,
                        "line": 33
                      }
                    },
                    {
                      "workerIndex": 1044,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/btl.spec.js:33:18",
                        "location": {
                          "file": "/var/www/360lm/tests/btl.spec.js",
                          "column": 18,
                          "line": 33
                        },
                        "snippet": "  31 |   test('CACHE_VER in source matches spec', async ({ request }) => {\n  32 |     const html = await (await request.get(BASE_URL)).text();\n> 33 |     expect(html).toContain(`CACHE_VER = '${SW_VER}'`);\n     |                  ^\n  34 |   });\n  35 |\n  36 |   test('partner login lands on My Claims with campaign name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 18,
                            "line": 33
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"CACHE_VER = 'btl-v1'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  31 |   test('CACHE_VER in source matches spec', async ({ request }) => {\n  32 |     const html = await (await request.get(BASE_URL)).text();\n> 33 |     expect(html).toContain(`CACHE_VER = '${SW_VER}'`);\n     |                  ^\n  34 |   });\n  35 |\n  36 |   test('partner login lands on My Claims with campaign name', async ({ page }) => {\n    at /var/www/360lm/tests/btl.spec.js:33:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:46:47.455Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Path-CACHE-VER-in-source-matches-spec-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/btl.spec.js",
                        "column": 18,
                        "line": 33
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-18d5375f6875665fbc45",
              "file": "btl.spec.js",
              "line": 31,
              "column": 3
            },
            {
              "title": "partner login lands on My Claims with campaign name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1045,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30094,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:37:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:46:48.292Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-337a1-y-Claims-with-campaign-name-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-337a1-y-Claims-with-campaign-name-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1046,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30109,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:37:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:47:19.312Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-337a1-y-Claims-with-campaign-name-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-337a1-y-Claims-with-campaign-name-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-8f23f0f5b9c6eea37d6b",
              "file": "btl.spec.js",
              "line": 36,
              "column": 3
            },
            {
              "title": "admin login lands on queue with campaign filter chips",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1047,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30095,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:44:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:47:50.520Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-8b7a7--with-campaign-filter-chips-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-8b7a7--with-campaign-filter-chips-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1048,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30073,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:44:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:48:21.578Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-8b7a7--with-campaign-filter-chips-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Happy-Pat-8b7a7--with-campaign-filter-chips-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-26fb176e55fdadf3d5a0",
              "file": "btl.spec.js",
              "line": 43,
              "column": 3
            }
          ]
        },
        {
          "title": "BTL Offers — Edge Cases",
          "file": "btl.spec.js",
          "line": 51,
          "column": 6,
          "specs": [
            {
              "title": "bad login shows error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 42,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:53:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:49:11.476Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Cases-bad-login-shows-error-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Cases-bad-login-shows-error-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 43,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30097,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:53:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:49:42.558Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Cases-bad-login-shows-error-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Cases-bad-login-shows-error-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-a39bbd272b25c021f8cf",
              "file": "btl.spec.js",
              "line": 52,
              "column": 3
            },
            {
              "title": "empty claim submit shows validation error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 44,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30089,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:59:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:50:13.694Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-893f5-bmit-shows-validation-error-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-893f5-bmit-shows-validation-error-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 45,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:59:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:50:44.706Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-893f5-bmit-shows-validation-error-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-893f5-bmit-shows-validation-error-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-a23590a1cf2086cafd6a",
              "file": "btl.spec.js",
              "line": 58,
              "column": 3
            },
            {
              "title": "amount field applies Indian formatting",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 46,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30083,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:69:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:51:15.703Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-93266-d-applies-Indian-formatting-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-93266-d-applies-Indian-formatting-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 47,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30085,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:69:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:51:46.764Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-93266-d-applies-Indian-formatting-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-93266-d-applies-Indian-formatting-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-b4ad574f359eac6f43b2",
              "file": "btl.spec.js",
              "line": 68,
              "column": 3
            },
            {
              "title": "bad login shows error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1049,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30126,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:53:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:48:52.682Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Cases-bad-login-shows-error-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Cases-bad-login-shows-error-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1050,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30073,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:53:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:49:23.777Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Cases-bad-login-shows-error-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Cases-bad-login-shows-error-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-dcf8bd9207a40c748e84",
              "file": "btl.spec.js",
              "line": 52,
              "column": 3
            },
            {
              "title": "empty claim submit shows validation error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1051,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30069,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:59:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:49:54.778Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-893f5-bmit-shows-validation-error-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-893f5-bmit-shows-validation-error-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1052,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30083,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:59:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:50:25.805Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-893f5-bmit-shows-validation-error-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-893f5-bmit-shows-validation-error-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-da54b51e99ff6b349016",
              "file": "btl.spec.js",
              "line": 58,
              "column": 3
            },
            {
              "title": "amount field applies Indian formatting",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1053,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30103,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:69:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:50:56.803Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-93266-d-applies-Indian-formatting-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-93266-d-applies-Indian-formatting-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1054,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30092,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:69:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:51:27.808Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-93266-d-applies-Indian-formatting-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Edge-Case-93266-d-applies-Indian-formatting-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-36c94a74aa609f579e86",
              "file": "btl.spec.js",
              "line": 68,
              "column": 3
            }
          ]
        },
        {
          "title": "BTL Offers — Sync & Data",
          "file": "btl.spec.js",
          "line": 77,
          "column": 6,
          "specs": [
            {
              "title": "claim draft survives page reload",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 48,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30091,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:79:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:52:17.856Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-claim-draft-survives-page-reload-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-claim-draft-survives-page-reload-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 49,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30090,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:79:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:52:48.979Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-claim-draft-survives-page-reload-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-claim-draft-survives-page-reload-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-bf7345e9ed2a2c89aed8",
              "file": "btl.spec.js",
              "line": 78,
              "column": 3
            },
            {
              "title": "offline: validation still works, network banner shows",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 50,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30084,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:94:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:53:20.120Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-a1ae2--works-network-banner-shows-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-a1ae2--works-network-banner-shows-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 51,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:94:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:53:51.151Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-a1ae2--works-network-banner-shows-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-a1ae2--works-network-banner-shows-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-5a5bea2efb528214ab8d",
              "file": "btl.spec.js",
              "line": 93,
              "column": 3
            },
            {
              "title": "claim draft survives page reload",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1055,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30094,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:79:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:51:58.910Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-claim-draft-survives-page-reload-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-claim-draft-survives-page-reload-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1056,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30099,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:79:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:52:30.067Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-claim-draft-survives-page-reload-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-claim-draft-survives-page-reload-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-810348af374ec1b80359",
              "file": "btl.spec.js",
              "line": 78,
              "column": 3
            },
            {
              "title": "offline: validation still works, network banner shows",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1057,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30133,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:94:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:53:01.462Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-a1ae2--works-network-banner-shows-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-a1ae2--works-network-banner-shows-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1058,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30132,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/btl.spec.js",
                            "column": 14,
                            "line": 18
                          },
                          "message": "Error: page.fill: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#lg-id')\u001b[22m\n\n\n  16 |   await page.evaluate(() => localStorage.clear());\n  17 |   await page.reload();\n> 18 |   await page.fill('#lg-id', who.id);\n     |              ^\n  19 |   await page.fill('#lg-pin', who.pin);\n  20 |   await page.click('#lg-btn');\n  21 | }\n    at login (/var/www/360lm/tests/btl.spec.js:18:14)\n    at /var/www/360lm/tests/btl.spec.js:94:5"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:53:32.675Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-a1ae2--works-network-banner-shows-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/btl-BTL-Offers-—-Sync-Data-a1ae2--works-network-banner-shows-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "65f746774e480eaa17a5-d2520f7be62fbbbfb8c2",
              "file": "btl.spec.js",
              "line": 93,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "client.spec.js",
      "file": "client.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Client Portal — Smoke",
          "file": "client.spec.js",
          "line": 22,
          "column": 6,
          "specs": [
            {
              "title": "page loads with login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 24,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 52,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 264,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:22.316Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 24,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-6673200f242f03a9618c",
              "file": "client.spec.js",
              "line": 23,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 52,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 223,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:33:28",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 28,
                          "line": 33
                        },
                        "snippet": "  31 |   test('manifest.json returns 200', async ({ page }) => {\n  32 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 33 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  34 |   });\n  35 |\n  36 |   test('sw.js contains current 360client version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 28,
                            "line": 33
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  31 |   test('manifest.json returns 200', async ({ page }) => {\n  32 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 33 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  34 |   });\n  35 |\n  36 |   test('sw.js contains current 360client version', async ({ page }) => {\n    at /var/www/360lm/tests/client.spec.js:33:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:22.747Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smoke-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smoke-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 28,
                        "line": 33
                      }
                    },
                    {
                      "workerIndex": 53,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 281,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:33:28",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 28,
                          "line": 33
                        },
                        "snippet": "  31 |   test('manifest.json returns 200', async ({ page }) => {\n  32 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 33 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  34 |   });\n  35 |\n  36 |   test('sw.js contains current 360client version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 28,
                            "line": 33
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  31 |   test('manifest.json returns 200', async ({ page }) => {\n  32 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 33 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  34 |   });\n  35 |\n  36 |   test('sw.js contains current 360client version', async ({ page }) => {\n    at /var/www/360lm/tests/client.spec.js:33:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:54:23.867Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smoke-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smoke-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 28,
                        "line": 33
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "9bf021294cad394269b8-e2d3d1fc80d0c0634628",
              "file": "client.spec.js",
              "line": 31,
              "column": 3
            },
            {
              "title": "sw.js contains current 360client version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 54,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 341,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:39:18",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 18,
                          "line": 39
                        },
                        "snippet": "  37 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  38 |     const text = await resp?.text() || '';\n> 39 |     expect(text).toMatch(/360client-v\\d+/);\n     |                  ^\n  40 |   });\n  41 | });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 18,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  37 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  38 |     const text = await resp?.text() || '';\n> 39 |     expect(text).toMatch(/360client-v\\d+/);\n     |                  ^\n  40 |   });\n  41 | });\n  42 |\n    at /var/www/360lm/tests/client.spec.js:39:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:25.099Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smo-ec6e6-s-current-360client-version-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smo-ec6e6-s-current-360client-version-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 18,
                        "line": 39
                      }
                    },
                    {
                      "workerIndex": 55,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 320,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:39:18",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 18,
                          "line": 39
                        },
                        "snippet": "  37 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  38 |     const text = await resp?.text() || '';\n> 39 |     expect(text).toMatch(/360client-v\\d+/);\n     |                  ^\n  40 |   });\n  41 | });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 18,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  37 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  38 |     const text = await resp?.text() || '';\n> 39 |     expect(text).toMatch(/360client-v\\d+/);\n     |                  ^\n  40 |   });\n  41 | });\n  42 |\n    at /var/www/360lm/tests/client.spec.js:39:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:54:26.482Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smo-ec6e6-s-current-360client-version-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smo-ec6e6-s-current-360client-version-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 18,
                        "line": 39
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "9bf021294cad394269b8-f25e6a4a92d384e3fabf",
              "file": "client.spec.js",
              "line": 36,
              "column": 3
            },
            {
              "title": "page loads with login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 24,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1059,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 328,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:04.137Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 24,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-63715a1901e190569f1a",
              "file": "client.spec.js",
              "line": 23,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1059,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 384,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:33:28",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 28,
                          "line": 33
                        },
                        "snippet": "  31 |   test('manifest.json returns 200', async ({ page }) => {\n  32 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 33 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  34 |   });\n  35 |\n  36 |   test('sw.js contains current 360client version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 28,
                            "line": 33
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  31 |   test('manifest.json returns 200', async ({ page }) => {\n  32 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 33 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  34 |   });\n  35 |\n  36 |   test('sw.js contains current 360client version', async ({ page }) => {\n    at /var/www/360lm/tests/client.spec.js:33:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:04.650Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smoke-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smoke-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 28,
                        "line": 33
                      }
                    },
                    {
                      "workerIndex": 1060,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 391,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:33:28",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 28,
                          "line": 33
                        },
                        "snippet": "  31 |   test('manifest.json returns 200', async ({ page }) => {\n  32 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 33 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  34 |   });\n  35 |\n  36 |   test('sw.js contains current 360client version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 28,
                            "line": 33
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  31 |   test('manifest.json returns 200', async ({ page }) => {\n  32 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 33 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  34 |   });\n  35 |\n  36 |   test('sw.js contains current 360client version', async ({ page }) => {\n    at /var/www/360lm/tests/client.spec.js:33:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:54:06.140Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 28,
                        "line": 33
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "9bf021294cad394269b8-13afabfa0cc0cf613582",
              "file": "client.spec.js",
              "line": 31,
              "column": 3
            },
            {
              "title": "sw.js contains current 360client version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1061,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 310,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:39:18",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 18,
                          "line": 39
                        },
                        "snippet": "  37 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  38 |     const text = await resp?.text() || '';\n> 39 |     expect(text).toMatch(/360client-v\\d+/);\n     |                  ^\n  40 |   });\n  41 | });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 18,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  37 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  38 |     const text = await resp?.text() || '';\n> 39 |     expect(text).toMatch(/360client-v\\d+/);\n     |                  ^\n  40 |   });\n  41 | });\n  42 |\n    at /var/www/360lm/tests/client.spec.js:39:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:07.657Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smo-ec6e6-s-current-360client-version-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smo-ec6e6-s-current-360client-version-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 18,
                        "line": 39
                      }
                    },
                    {
                      "workerIndex": 1062,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 255,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/client.spec.js:39:18",
                        "location": {
                          "file": "/var/www/360lm/tests/client.spec.js",
                          "column": 18,
                          "line": 39
                        },
                        "snippet": "  37 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  38 |     const text = await resp?.text() || '';\n> 39 |     expect(text).toMatch(/360client-v\\d+/);\n     |                  ^\n  40 |   });\n  41 | });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "column": 18,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  37 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  38 |     const text = await resp?.text() || '';\n> 39 |     expect(text).toMatch(/360client-v\\d+/);\n     |                  ^\n  40 |   });\n  41 | });\n  42 |\n    at /var/www/360lm/tests/client.spec.js:39:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:54:08.999Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smo-ec6e6-s-current-360client-version-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/client-Client-Portal-—-Smo-ec6e6-s-current-360client-version-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "column": 18,
                        "line": 39
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "9bf021294cad394269b8-8a8c93c43dc892d4ddc8",
              "file": "client.spec.js",
              "line": 36,
              "column": 3
            }
          ]
        },
        {
          "title": "Client Portal — HTML Structure",
          "file": "client.spec.js",
          "line": 44,
          "column": 6,
          "specs": [
            {
              "title": "login screen has PIN pad, Client ID tab, Company Code tab",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 53,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:27.807Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-c507f4e26462d12cbf7b",
              "file": "client.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "screens for home, recce, recce-approval, responses, acct detail all exist",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:27.905Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-b2797db2faa043b1f639",
              "file": "client.spec.js",
              "line": 59,
              "column": 3
            },
            {
              "title": "New Account modal has zone dropdown and is_head toggle",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:27.941Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-74bebc694757190e57c7",
              "file": "client.spec.js",
              "line": 72,
              "column": 3
            },
            {
              "title": "Edit Zone / Head Status modal exists",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:27.972Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-159814b2965731e8d4ee",
              "file": "client.spec.js",
              "line": 81,
              "column": 3
            },
            {
              "title": "Add Grant modal contains campaign option",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:27.988Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-4eed6b054048eab752c7",
              "file": "client.spec.js",
              "line": 90,
              "column": 3
            },
            {
              "title": "account detail has zone-row and Edit Zone button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.030Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-5ac6475027724d20203c",
              "file": "client.spec.js",
              "line": 98,
              "column": 3
            },
            {
              "title": "login screen has PIN pad, Client ID tab, Company Code tab",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 56,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.300Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-406000ccec728f206e6e",
              "file": "client.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "screens for home, recce, recce-approval, responses, acct detail all exist",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.411Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-6bc462dcc1830f1b7a50",
              "file": "client.spec.js",
              "line": 59,
              "column": 3
            },
            {
              "title": "New Account modal has zone dropdown and is_head toggle",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.452Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-d56a6e55479d626f51d6",
              "file": "client.spec.js",
              "line": 72,
              "column": 3
            },
            {
              "title": "Edit Zone / Head Status modal exists",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.487Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-97bd3eed8c02f81802a6",
              "file": "client.spec.js",
              "line": 81,
              "column": 3
            },
            {
              "title": "Add Grant modal contains campaign option",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.503Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-c85ebc4a5735290b776d",
              "file": "client.spec.js",
              "line": 90,
              "column": 3
            },
            {
              "title": "account detail has zone-row and Edit Zone button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 46,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.534Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 46,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-097f06085d77205d1c57",
              "file": "client.spec.js",
              "line": 98,
              "column": 3
            }
          ]
        },
        {
          "title": "Client Portal — Zone-Aware Access",
          "file": "client.spec.js",
          "line": 109,
          "column": 6,
          "specs": [
            {
              "title": "source contains INST_RD header for installation schema",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 111,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.080Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 111,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-353e7135eaaeb7691dc1",
              "file": "client.spec.js",
              "line": 114,
              "column": 3
            },
            {
              "title": "source contains computeAccessibleCampaigns and isHead logic",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 111,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.094Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 111,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-f472587cf5982a0bf062",
              "file": "client.spec.js",
              "line": 122,
              "column": 3
            },
            {
              "title": "session enrichment: separate account fetch for zone/isHead",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 111,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.114Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 111,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-7bcea2a67c7d740274e5",
              "file": "client.spec.js",
              "line": 133,
              "column": 3
            },
            {
              "title": "source contains _STATUS_CFG for client_status badges",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 111,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.129Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 111,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-c634bd6eae3b728575a7",
              "file": "client.spec.js",
              "line": 142,
              "column": 3
            },
            {
              "title": "enterRecce fetches by campaign_id not client_name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 111,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.154Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 111,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-c063fa211f9386cbe422",
              "file": "client.spec.js",
              "line": 152,
              "column": 3
            },
            {
              "title": "source contains INST_RD header for installation schema",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 111,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.563Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 111,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-48830869083c9fd762d5",
              "file": "client.spec.js",
              "line": 114,
              "column": 3
            },
            {
              "title": "source contains computeAccessibleCampaigns and isHead logic",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 111,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.574Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 111,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-5ef147e6b876c87cf672",
              "file": "client.spec.js",
              "line": 122,
              "column": 3
            },
            {
              "title": "session enrichment: separate account fetch for zone/isHead",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 111,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.592Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 111,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-fa93aa2530e3667f91cd",
              "file": "client.spec.js",
              "line": 133,
              "column": 3
            },
            {
              "title": "source contains _STATUS_CFG for client_status badges",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 111,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.603Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 111,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-cde1a27bb291a4e9741a",
              "file": "client.spec.js",
              "line": 142,
              "column": 3
            },
            {
              "title": "enterRecce fetches by campaign_id not client_name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 111,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.617Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 111,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-8790c42dea96b25ff8dd",
              "file": "client.spec.js",
              "line": 152,
              "column": 3
            }
          ]
        },
        {
          "title": "Client Portal — Recce Approval Flow",
          "file": "client.spec.js",
          "line": 164,
          "column": 6,
          "specs": [
            {
              "title": "source contains enterRecceApproval and approval helpers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.167Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-845e854dc57bd868981c",
              "file": "client.spec.js",
              "line": 169,
              "column": 3
            },
            {
              "title": "source contains all three approval option handlers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.180Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-7edfeac7d9e22b7138dd",
              "file": "client.spec.js",
              "line": 182,
              "column": 3
            },
            {
              "title": "source contains branding approval row insertion with change detection",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.193Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-c5b78a2f413438bff417",
              "file": "client.spec.js",
              "line": 191,
              "column": 3
            },
            {
              "title": "source contains fire-and-forget PDF call with TODO marker",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.208Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-875447fe0fd70199012c",
              "file": "client.spec.js",
              "line": 204,
              "column": 3
            },
            {
              "title": "approval screen loads (unauthenticated shows login, not crash)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.225Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-a712bad3c7c6d0702515",
              "file": "client.spec.js",
              "line": 212,
              "column": 3
            },
            {
              "title": "source contains loadBrandingApprovalResults for read-only view",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.241Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-d8c575235d0b02ff7fd9",
              "file": "client.spec.js",
              "line": 220,
              "column": 3
            },
            {
              "title": "source contains enterRecceApproval and approval helpers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.633Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-efda3571c55d1c8cb55c",
              "file": "client.spec.js",
              "line": 169,
              "column": 3
            },
            {
              "title": "source contains all three approval option handlers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.654Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-4c67a6726d29f6a23ebc",
              "file": "client.spec.js",
              "line": 182,
              "column": 3
            },
            {
              "title": "source contains branding approval row insertion with change detection",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.667Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-ad13a0cc460004e4c45e",
              "file": "client.spec.js",
              "line": 191,
              "column": 3
            },
            {
              "title": "source contains fire-and-forget PDF call with TODO marker",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.680Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-0da3600c76825e3b4cf6",
              "file": "client.spec.js",
              "line": 204,
              "column": 3
            },
            {
              "title": "approval screen loads (unauthenticated shows login, not crash)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.693Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-87da26419baf793ab51e",
              "file": "client.spec.js",
              "line": 212,
              "column": 3
            },
            {
              "title": "source contains loadBrandingApprovalResults for read-only view",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 166,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.703Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 166,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-aab50e063a54ed6ff433",
              "file": "client.spec.js",
              "line": 220,
              "column": 3
            }
          ]
        },
        {
          "title": "Client Portal — Account Zone Management",
          "file": "client.spec.js",
          "line": 231,
          "column": 6,
          "specs": [
            {
              "title": "source contains openEditZoneHead and saveZoneHead",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 233,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.262Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 233,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-11cb0104fc2350a47863",
              "file": "client.spec.js",
              "line": 236,
              "column": 3
            },
            {
              "title": "source contains zone/isHead CSS classes",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 233,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.275Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 233,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-470d86cd765f49cd2dba",
              "file": "client.spec.js",
              "line": 246,
              "column": 3
            },
            {
              "title": "openNewAcct is async and loads zones",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 233,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.289Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 233,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-ba7fd336da02e6a7a860",
              "file": "client.spec.js",
              "line": 255,
              "column": 3
            },
            {
              "title": "source contains openEditZoneHead and saveZoneHead",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 233,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.715Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 233,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-75eb426536cb83f79c87",
              "file": "client.spec.js",
              "line": 236,
              "column": 3
            },
            {
              "title": "source contains zone/isHead CSS classes",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 233,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.729Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 233,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-e1668fa907c612b3ff20",
              "file": "client.spec.js",
              "line": 246,
              "column": 3
            },
            {
              "title": "openNewAcct is async and loads zones",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Client Portal HTML not yet built — placeholder only",
                      "location": {
                        "file": "/var/www/360lm/tests/client.spec.js",
                        "line": 233,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.742Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Client Portal HTML not yet built — placeholder only",
                          "location": {
                            "file": "/var/www/360lm/tests/client.spec.js",
                            "line": 233,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "9bf021294cad394269b8-19851dbf66d74ec1a354",
              "file": "client.spec.js",
              "line": 255,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "contacts.spec.js",
      "file": "contacts.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Contacts — Smoke",
          "file": "contacts.spec.js",
          "line": 9,
          "column": 6,
          "specs": [
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1044,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:28.308Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-d8290088f76bb9c6c056",
              "file": "contacts.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 56,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 185,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/contacts.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:29.484Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 57,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 280,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/contacts.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:54:30.520Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-5fe6125e5c2915f9bee0",
              "file": "contacts.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 58,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 315,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('contacts-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('contacts-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/contacts.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:31.901Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-sw-js-is-versioned-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-sw-js-is-versioned-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 59,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 294,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('contacts-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('contacts-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/contacts.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:54:33.194Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-sw-js-is-versioned-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-sw-js-is-versioned-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-d510c43eafa50f5c34cc",
              "file": "contacts.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1017,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:10.760Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-3a08c2a231f97b83f696",
              "file": "contacts.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1063,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 186,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/contacts.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:11.901Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 1064,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 291,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/contacts.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:54:12.979Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-289d44f711c94eb472b0",
              "file": "contacts.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1065,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 275,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('contacts-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('contacts-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/contacts.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:14.299Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-sw-js-is-versioned-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-sw-js-is-versioned-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 1066,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 311,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('contacts-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"contacts-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('contacts-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/contacts.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:54:15.571Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-sw-js-is-versioned-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-Smoke-sw-js-is-versioned-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-0d4e591c828507bb9b44",
              "file": "contacts.spec.js",
              "line": 22,
              "column": 3
            }
          ]
        },
        {
          "title": "Contacts — HTML Structure",
          "file": "contacts.spec.js",
          "line": 29,
          "column": 6,
          "specs": [
            {
              "title": "auth screen and app container present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 60,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 12,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('auth screen and app container present', () => {\n> 40 |     expect(html).toContain('id=\"auth-screen\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |   });\n  43 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('auth screen and app container present', () => {\n> 40 |     expect(html).toContain('id=\"auth-screen\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |   });\n  43 |\n    at /var/www/360lm/tests/contacts.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:34.431Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-4b31b-n-and-app-container-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    },
                    {
                      "workerIndex": 61,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('auth screen and app container present', () => {\n> 40 |     expect(html).toContain('id=\"auth-screen\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |   });\n  43 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('auth screen and app container present', () => {\n> 40 |     expect(html).toContain('id=\"auth-screen\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |   });\n  43 |\n    at /var/www/360lm/tests/contacts.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:54:35.291Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-4b31b-n-and-app-container-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-af05dab0e89443ebdba1",
              "file": "contacts.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub navigation button present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 62,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('hub navigation button present', () => {\n> 45 |     expect(html).toContain(\"location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('hub navigation button present', () => {\n> 45 |     expect(html).toContain(\"location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/contacts.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:36.164Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-de7bd-b-navigation-button-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    },
                    {
                      "workerIndex": 63,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('hub navigation button present', () => {\n> 45 |     expect(html).toContain(\"location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('hub navigation button present', () => {\n> 45 |     expect(html).toContain(\"location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/contacts.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:54:37.118Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-de7bd-b-navigation-button-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-fbb589e6bb01998384bb",
              "file": "contacts.spec.js",
              "line": 44,
              "column": 3
            },
            {
              "title": "search and contact list elements present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 64,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('search and contact list elements present', () => {\n> 50 |     expect(html).toContain('id=\"contact-list\"');\n     |                  ^\n  51 |     expect(html).toContain('id=\"q\"');\n  52 |     expect(html).toContain('id=\"stat-bar\"');\n  53 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('search and contact list elements present', () => {\n> 50 |     expect(html).toContain('id=\"contact-list\"');\n     |                  ^\n  51 |     expect(html).toContain('id=\"q\"');\n  52 |     expect(html).toContain('id=\"stat-bar\"');\n  53 |   });\n    at /var/www/360lm/tests/contacts.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:38.081Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-afbcf-ntact-list-elements-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    },
                    {
                      "workerIndex": 65,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('search and contact list elements present', () => {\n> 50 |     expect(html).toContain('id=\"contact-list\"');\n     |                  ^\n  51 |     expect(html).toContain('id=\"q\"');\n  52 |     expect(html).toContain('id=\"stat-bar\"');\n  53 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('search and contact list elements present', () => {\n> 50 |     expect(html).toContain('id=\"contact-list\"');\n     |                  ^\n  51 |     expect(html).toContain('id=\"q\"');\n  52 |     expect(html).toContain('id=\"stat-bar\"');\n  53 |   });\n    at /var/www/360lm/tests/contacts.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:54:39.016Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-afbcf-ntact-list-elements-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-512f4926015d1716c570",
              "file": "contacts.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "Dexie offline DB used",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 66,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 21,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:56:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 56
                        },
                        "snippet": "  54 |\n  55 |   test('Dexie offline DB used', () => {\n> 56 |     expect(html).toContain(\"new Dexie('contacts360')\");\n     |                  ^\n  57 |   });\n  58 |\n  59 |   test('Top Management access control in place', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 56
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  54 |\n  55 |   test('Dexie offline DB used', () => {\n> 56 |     expect(html).toContain(\"new Dexie('contacts360')\");\n     |                  ^\n  57 |   });\n  58 |\n  59 |   test('Top Management access control in place', () => {\n    at /var/www/360lm/tests/contacts.spec.js:56:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:39.880Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-Structure-Dexie-offline-DB-used-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 56
                      }
                    },
                    {
                      "workerIndex": 67,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:56:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 56
                        },
                        "snippet": "  54 |\n  55 |   test('Dexie offline DB used', () => {\n> 56 |     expect(html).toContain(\"new Dexie('contacts360')\");\n     |                  ^\n  57 |   });\n  58 |\n  59 |   test('Top Management access control in place', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 56
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  54 |\n  55 |   test('Dexie offline DB used', () => {\n> 56 |     expect(html).toContain(\"new Dexie('contacts360')\");\n     |                  ^\n  57 |   });\n  58 |\n  59 |   test('Top Management access control in place', () => {\n    at /var/www/360lm/tests/contacts.spec.js:56:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:54:40.805Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-Structure-Dexie-offline-DB-used-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 56
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-69d326423f253c59071c",
              "file": "contacts.spec.js",
              "line": 55,
              "column": 3
            },
            {
              "title": "Top Management access control in place",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 68,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:60:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 60
                        },
                        "snippet": "  58 |\n  59 |   test('Top Management access control in place', () => {\n> 60 |     expect(html).toContain('TM_IDS');\n     |                  ^\n  61 |     expect(html).toContain(\"'harish'\");\n  62 |     expect(html).toContain(\"'pramod'\");\n  63 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  58 |\n  59 |   test('Top Management access control in place', () => {\n> 60 |     expect(html).toContain('TM_IDS');\n     |                  ^\n  61 |     expect(html).toContain(\"'harish'\");\n  62 |     expect(html).toContain(\"'pramod'\");\n  63 |   });\n    at /var/www/360lm/tests/contacts.spec.js:60:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:41.665Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-75fee-ent-access-control-in-place-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 60
                      }
                    },
                    {
                      "workerIndex": 69,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 12,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:60:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 60
                        },
                        "snippet": "  58 |\n  59 |   test('Top Management access control in place', () => {\n> 60 |     expect(html).toContain('TM_IDS');\n     |                  ^\n  61 |     expect(html).toContain(\"'harish'\");\n  62 |     expect(html).toContain(\"'pramod'\");\n  63 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  58 |\n  59 |   test('Top Management access control in place', () => {\n> 60 |     expect(html).toContain('TM_IDS');\n     |                  ^\n  61 |     expect(html).toContain(\"'harish'\");\n  62 |     expect(html).toContain(\"'pramod'\");\n  63 |   });\n    at /var/www/360lm/tests/contacts.spec.js:60:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:54:42.593Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-75fee-ent-access-control-in-place-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 60
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-3092d13f8713ce8a9cb1",
              "file": "contacts.spec.js",
              "line": 59,
              "column": 3
            },
            {
              "title": "auth screen and app container present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1067,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 17,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('auth screen and app container present', () => {\n> 40 |     expect(html).toContain('id=\"auth-screen\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |   });\n  43 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('auth screen and app container present', () => {\n> 40 |     expect(html).toContain('id=\"auth-screen\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |   });\n  43 |\n    at /var/www/360lm/tests/contacts.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:16.826Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-4b31b-n-and-app-container-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    },
                    {
                      "workerIndex": 1068,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('auth screen and app container present', () => {\n> 40 |     expect(html).toContain('id=\"auth-screen\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |   });\n  43 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"auth-screen\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('auth screen and app container present', () => {\n> 40 |     expect(html).toContain('id=\"auth-screen\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |   });\n  43 |\n    at /var/www/360lm/tests/contacts.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:54:17.684Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-4b31b-n-and-app-container-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-12dc63ca8aecf26d37af",
              "file": "contacts.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub navigation button present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1069,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('hub navigation button present', () => {\n> 45 |     expect(html).toContain(\"location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('hub navigation button present', () => {\n> 45 |     expect(html).toContain(\"location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/contacts.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:18.567Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-de7bd-b-navigation-button-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    },
                    {
                      "workerIndex": 1070,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('hub navigation button present', () => {\n> 45 |     expect(html).toContain(\"location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('hub navigation button present', () => {\n> 45 |     expect(html).toContain(\"location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/contacts.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:54:19.460Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-de7bd-b-navigation-button-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-d861c76c63526b235108",
              "file": "contacts.spec.js",
              "line": 44,
              "column": 3
            },
            {
              "title": "search and contact list elements present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1071,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('search and contact list elements present', () => {\n> 50 |     expect(html).toContain('id=\"contact-list\"');\n     |                  ^\n  51 |     expect(html).toContain('id=\"q\"');\n  52 |     expect(html).toContain('id=\"stat-bar\"');\n  53 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('search and contact list elements present', () => {\n> 50 |     expect(html).toContain('id=\"contact-list\"');\n     |                  ^\n  51 |     expect(html).toContain('id=\"q\"');\n  52 |     expect(html).toContain('id=\"stat-bar\"');\n  53 |   });\n    at /var/www/360lm/tests/contacts.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:20.327Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-afbcf-ntact-list-elements-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    },
                    {
                      "workerIndex": 1072,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('search and contact list elements present', () => {\n> 50 |     expect(html).toContain('id=\"contact-list\"');\n     |                  ^\n  51 |     expect(html).toContain('id=\"q\"');\n  52 |     expect(html).toContain('id=\"stat-bar\"');\n  53 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"contact-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('search and contact list elements present', () => {\n> 50 |     expect(html).toContain('id=\"contact-list\"');\n     |                  ^\n  51 |     expect(html).toContain('id=\"q\"');\n  52 |     expect(html).toContain('id=\"stat-bar\"');\n  53 |   });\n    at /var/www/360lm/tests/contacts.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:54:21.304Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-afbcf-ntact-list-elements-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-a4fe3d55fc0a17d9cfa4",
              "file": "contacts.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "Dexie offline DB used",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1073,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 23,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:56:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 56
                        },
                        "snippet": "  54 |\n  55 |   test('Dexie offline DB used', () => {\n> 56 |     expect(html).toContain(\"new Dexie('contacts360')\");\n     |                  ^\n  57 |   });\n  58 |\n  59 |   test('Top Management access control in place', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 56
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  54 |\n  55 |   test('Dexie offline DB used', () => {\n> 56 |     expect(html).toContain(\"new Dexie('contacts360')\");\n     |                  ^\n  57 |   });\n  58 |\n  59 |   test('Top Management access control in place', () => {\n    at /var/www/360lm/tests/contacts.spec.js:56:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:22.246Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-Structure-Dexie-offline-DB-used-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 56
                      }
                    },
                    {
                      "workerIndex": 1074,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 19,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:56:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 56
                        },
                        "snippet": "  54 |\n  55 |   test('Dexie offline DB used', () => {\n> 56 |     expect(html).toContain(\"new Dexie('contacts360')\");\n     |                  ^\n  57 |   });\n  58 |\n  59 |   test('Top Management access control in place', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 56
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"new Dexie('contacts360')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  54 |\n  55 |   test('Dexie offline DB used', () => {\n> 56 |     expect(html).toContain(\"new Dexie('contacts360')\");\n     |                  ^\n  57 |   });\n  58 |\n  59 |   test('Top Management access control in place', () => {\n    at /var/www/360lm/tests/contacts.spec.js:56:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:54:23.136Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-Structure-Dexie-offline-DB-used-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 56
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-ec6c2ec8510ec0631cc6",
              "file": "contacts.spec.js",
              "line": 55,
              "column": 3
            },
            {
              "title": "Top Management access control in place",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1075,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:60:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 60
                        },
                        "snippet": "  58 |\n  59 |   test('Top Management access control in place', () => {\n> 60 |     expect(html).toContain('TM_IDS');\n     |                  ^\n  61 |     expect(html).toContain(\"'harish'\");\n  62 |     expect(html).toContain(\"'pramod'\");\n  63 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  58 |\n  59 |   test('Top Management access control in place', () => {\n> 60 |     expect(html).toContain('TM_IDS');\n     |                  ^\n  61 |     expect(html).toContain(\"'harish'\");\n  62 |     expect(html).toContain(\"'pramod'\");\n  63 |   });\n    at /var/www/360lm/tests/contacts.spec.js:60:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:24.024Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-75fee-ent-access-control-in-place-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 60
                      }
                    },
                    {
                      "workerIndex": 1076,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/contacts.spec.js:60:18",
                        "location": {
                          "file": "/var/www/360lm/tests/contacts.spec.js",
                          "column": 18,
                          "line": 60
                        },
                        "snippet": "  58 |\n  59 |   test('Top Management access control in place', () => {\n> 60 |     expect(html).toContain('TM_IDS');\n     |                  ^\n  61 |     expect(html).toContain(\"'harish'\");\n  62 |     expect(html).toContain(\"'pramod'\");\n  63 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/contacts.spec.js",
                            "column": 18,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"TM_IDS\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  58 |\n  59 |   test('Top Management access control in place', () => {\n> 60 |     expect(html).toContain('TM_IDS');\n     |                  ^\n  61 |     expect(html).toContain(\"'harish'\");\n  62 |     expect(html).toContain(\"'pramod'\");\n  63 |   });\n    at /var/www/360lm/tests/contacts.spec.js:60:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:54:24.925Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/contacts-Contacts-—-HTML-S-75fee-ent-access-control-in-place-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/contacts.spec.js",
                        "column": 18,
                        "line": 60
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "d36c0a2d018a09404ff9-8687c4a9af129781389c",
              "file": "contacts.spec.js",
              "line": 59,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "counters.spec.js",
      "file": "counters.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Counters — Smoke",
          "file": "counters.spec.js",
          "line": 37,
          "column": 6,
          "specs": [
            {
              "title": "page returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 664,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:43.523Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-d829f7586921bf7aef93",
              "file": "counters.spec.js",
              "line": 38,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 189,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:44.386Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-7bd6bf0f323af6d53a50",
              "file": "counters.spec.js",
              "line": 43,
              "column": 3
            },
            {
              "title": "HTML carries counters SW version",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 54,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:44.611Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-be7c205fcb4dae1c676a",
              "file": "counters.spec.js",
              "line": 48,
              "column": 3
            },
            {
              "title": "PostgREST exposes counter + counter_v under Accept-Profile: counters",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 105,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:44.690Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-d03142fa3bda2eb2551f",
              "file": "counters.spec.js",
              "line": 56,
              "column": 3
            },
            {
              "title": "page returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 564,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:25.865Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-6875686340d6809f4426",
              "file": "counters.spec.js",
              "line": 38,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 176,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:26.595Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-a0552e256cebcf7f16a1",
              "file": "counters.spec.js",
              "line": 43,
              "column": 3
            },
            {
              "title": "HTML carries counters SW version",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 47,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:26.809Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-db731f5ffc5519a68276",
              "file": "counters.spec.js",
              "line": 48,
              "column": 3
            },
            {
              "title": "PostgREST exposes counter + counter_v under Accept-Profile: counters",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 67,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:26.878Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-3c9d7f9a3e4739385b39",
              "file": "counters.spec.js",
              "line": 56,
              "column": 3
            }
          ]
        },
        {
          "title": "Counters — HTML structure",
          "file": "counters.spec.js",
          "line": 69,
          "column": 6,
          "specs": [
            {
              "title": "three main screens declared",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:44.826Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-43ecfffa02315f1aa05b",
              "file": "counters.spec.js",
              "line": 77,
              "column": 3
            },
            {
              "title": "contact + address modals present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:44.886Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-53967334be53e7c20127",
              "file": "counters.spec.js",
              "line": 83,
              "column": 3
            },
            {
              "title": "shared safe-bottom CSS linked",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:44.907Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-c5ee9a04fde69373edfe",
              "file": "counters.spec.js",
              "line": 90,
              "column": 3
            },
            {
              "title": "hub ?next= redirect implemented",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:44.919Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-f6391283a00b9fd5ef72",
              "file": "counters.spec.js",
              "line": 94,
              "column": 3
            },
            {
              "title": "header has Hub home button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:44.936Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-e2bc9fb6110f8285dfda",
              "file": "counters.spec.js",
              "line": 99,
              "column": 3
            },
            {
              "title": "all required form fields present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 17,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:44.950Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-1a414c7dd03d27580e50",
              "file": "counters.spec.js",
              "line": 104,
              "column": 3
            },
            {
              "title": "brand chip editor + dedup panel present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:44.979Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-8546d5546b914b4fc2b8",
              "file": "counters.spec.js",
              "line": 116,
              "column": 3
            },
            {
              "title": "Phase 2 — allocation modal + upload queue screen present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 7,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:45.000Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-418522d2cbe34d8213e1",
              "file": "counters.spec.js",
              "line": 123,
              "column": 3
            },
            {
              "title": "Phase 3 — Multi-Recce review screen + pick official present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 7,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:45.020Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-9d45dcc20040e0f2d2f9",
              "file": "counters.spec.js",
              "line": 134,
              "column": 3
            },
            {
              "title": "Phase 4.5 — clients admin screen + modals + RPCs referenced",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:45.042Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-2e8b8ae9853185d22860",
              "file": "counters.spec.js",
              "line": 144,
              "column": 3
            },
            {
              "title": "Phase 4.6 — notifications screen + RPCs referenced",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 7,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:45.069Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-cae996ca5eb363cd192d",
              "file": "counters.spec.js",
              "line": 159,
              "column": 3
            },
            {
              "title": "Phase 4.7 — audit screen + RPCs referenced",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:45.090Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-3715a5e336f47f010c8b",
              "file": "counters.spec.js",
              "line": 170,
              "column": 3
            },
            {
              "title": "Phase 4.8 — sessions modal + RPCs referenced",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:45.107Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-b306a236d69b03faf755",
              "file": "counters.spec.js",
              "line": 181,
              "column": 3
            },
            {
              "title": "Phase 4.11 — risk chip styles + audit risk filter + suspicious_login meta",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:45.123Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-38388647f8263832dead",
              "file": "counters.spec.js",
              "line": 189,
              "column": 3
            },
            {
              "title": "Phase 4.12 — audit export + prune UI wired",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:45.139Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-daa60991c55b8cc7ed10",
              "file": "counters.spec.js",
              "line": 199,
              "column": 3
            },
            {
              "title": "Phase 4.14 — audit stats modal + RPC referenced",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:45.152Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-ea4c9bd4b6c3efc7ea44",
              "file": "counters.spec.js",
              "line": 208,
              "column": 3
            },
            {
              "title": "Phase 4.15 — supervisor_weekly_digest event meta wired",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:45.167Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-b372d36e8e1bcc75ec51",
              "file": "counters.spec.js",
              "line": 217,
              "column": 3
            },
            {
              "title": "Phase 4.17 — comments modal + RPCs referenced",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 70,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 39,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:229:18",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 18,
                          "line": 229
                        },
                        "snippet": "  227 |     expect(html).toContain('add_comment_as_supervisor');\n  228 |     expect(html).toContain('Recce comment');\n> 229 |     expect(html).toContain('💬 Comments');\n      |                  ^\n  230 |   });\n  231 |\n  232 |   test('Phase 4.18 — Sessions modal references pending-invite admin RPC', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 18,
                            "line": 229
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  227 |     expect(html).toContain('add_comment_as_supervisor');\n  228 |     expect(html).toContain('Recce comment');\n> 229 |     expect(html).toContain('💬 Comments');\n      |                  ^\n  230 |   });\n  231 |\n  232 |   test('Phase 4.18 — Sessions modal references pending-invite admin RPC', () => {\n    at /var/www/360lm/tests/counters.spec.js:229:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:45.178Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-HTML-s-76e62-ments-modal-RPCs-referenced-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 18,
                        "line": 229
                      }
                    },
                    {
                      "workerIndex": 71,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:229:18",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 18,
                          "line": 229
                        },
                        "snippet": "  227 |     expect(html).toContain('add_comment_as_supervisor');\n  228 |     expect(html).toContain('Recce comment');\n> 229 |     expect(html).toContain('💬 Comments');\n      |                  ^\n  230 |   });\n  231 |\n  232 |   test('Phase 4.18 — Sessions modal references pending-invite admin RPC', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 18,
                            "line": 229
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  227 |     expect(html).toContain('add_comment_as_supervisor');\n  228 |     expect(html).toContain('Recce comment');\n> 229 |     expect(html).toContain('💬 Comments');\n      |                  ^\n  230 |   });\n  231 |\n  232 |   test('Phase 4.18 — Sessions modal references pending-invite admin RPC', () => {\n    at /var/www/360lm/tests/counters.spec.js:229:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:54:46.095Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-HTML-s-76e62-ments-modal-RPCs-referenced-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 18,
                        "line": 229
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-f83797d15868913ff78a",
              "file": "counters.spec.js",
              "line": 222,
              "column": 3
            },
            {
              "title": "Phase 4.18 — Sessions modal references pending-invite admin RPC",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 72,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 27,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:47.236Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-d799f69e078ab7ff5ca7",
              "file": "counters.spec.js",
              "line": 232,
              "column": 3
            },
            {
              "title": "Phase 4.0a — signed-URL viewer wired (no direct window.open on view_url)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 72,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:47.404Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-1fd6859daa9302c39ecb",
              "file": "counters.spec.js",
              "line": 240,
              "column": 3
            },
            {
              "title": "three main screens declared",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:26.960Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-7487f89d18d63385945a",
              "file": "counters.spec.js",
              "line": 77,
              "column": 3
            },
            {
              "title": "contact + address modals present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.014Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-f3e39a9f9f236b02c171",
              "file": "counters.spec.js",
              "line": 83,
              "column": 3
            },
            {
              "title": "shared safe-bottom CSS linked",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.035Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-54fb364f5fa32c74b375",
              "file": "counters.spec.js",
              "line": 90,
              "column": 3
            },
            {
              "title": "hub ?next= redirect implemented",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.047Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-319c3c9d834801bd6bd9",
              "file": "counters.spec.js",
              "line": 94,
              "column": 3
            },
            {
              "title": "header has Hub home button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.062Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-a960d02c7646c5158a48",
              "file": "counters.spec.js",
              "line": 99,
              "column": 3
            },
            {
              "title": "all required form fields present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.075Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-96d623ef7095f6dc464b",
              "file": "counters.spec.js",
              "line": 104,
              "column": 3
            },
            {
              "title": "brand chip editor + dedup panel present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.099Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-de96c7a78867e88cd440",
              "file": "counters.spec.js",
              "line": 116,
              "column": 3
            },
            {
              "title": "Phase 2 — allocation modal + upload queue screen present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.111Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-ca79910b571b8cf083fe",
              "file": "counters.spec.js",
              "line": 123,
              "column": 3
            },
            {
              "title": "Phase 3 — Multi-Recce review screen + pick official present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.125Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-d51ca09aeeff396b76e1",
              "file": "counters.spec.js",
              "line": 134,
              "column": 3
            },
            {
              "title": "Phase 4.5 — clients admin screen + modals + RPCs referenced",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 8,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.141Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-4e87ce359772c61f48a2",
              "file": "counters.spec.js",
              "line": 144,
              "column": 3
            },
            {
              "title": "Phase 4.6 — notifications screen + RPCs referenced",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.158Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-0934d45f10647b6ccb39",
              "file": "counters.spec.js",
              "line": 159,
              "column": 3
            },
            {
              "title": "Phase 4.7 — audit screen + RPCs referenced",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.174Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-7028d47892605a0d79ac",
              "file": "counters.spec.js",
              "line": 170,
              "column": 3
            },
            {
              "title": "Phase 4.8 — sessions modal + RPCs referenced",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.189Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-72eace60eb6f7f8a2413",
              "file": "counters.spec.js",
              "line": 181,
              "column": 3
            },
            {
              "title": "Phase 4.11 — risk chip styles + audit risk filter + suspicious_login meta",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.201Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-468ed4c5e47f305902e0",
              "file": "counters.spec.js",
              "line": 189,
              "column": 3
            },
            {
              "title": "Phase 4.12 — audit export + prune UI wired",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.215Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-33dc7e501768f0dbe350",
              "file": "counters.spec.js",
              "line": 199,
              "column": 3
            },
            {
              "title": "Phase 4.14 — audit stats modal + RPC referenced",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.229Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-a7060188f7c092f9334e",
              "file": "counters.spec.js",
              "line": 208,
              "column": 3
            },
            {
              "title": "Phase 4.15 — supervisor_weekly_digest event meta wired",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.247Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-7230ac6740beafb552ec",
              "file": "counters.spec.js",
              "line": 217,
              "column": 3
            },
            {
              "title": "Phase 4.17 — comments modal + RPCs referenced",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1077,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 49,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:229:18",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 18,
                          "line": 229
                        },
                        "snippet": "  227 |     expect(html).toContain('add_comment_as_supervisor');\n  228 |     expect(html).toContain('Recce comment');\n> 229 |     expect(html).toContain('💬 Comments');\n      |                  ^\n  230 |   });\n  231 |\n  232 |   test('Phase 4.18 — Sessions modal references pending-invite admin RPC', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 18,
                            "line": 229
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  227 |     expect(html).toContain('add_comment_as_supervisor');\n  228 |     expect(html).toContain('Recce comment');\n> 229 |     expect(html).toContain('💬 Comments');\n      |                  ^\n  230 |   });\n  231 |\n  232 |   test('Phase 4.18 — Sessions modal references pending-invite admin RPC', () => {\n    at /var/www/360lm/tests/counters.spec.js:229:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:27.261Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-HTML-s-76e62-ments-modal-RPCs-referenced-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 18,
                        "line": 229
                      }
                    },
                    {
                      "workerIndex": 1078,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 82,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:229:18",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 18,
                          "line": 229
                        },
                        "snippet": "  227 |     expect(html).toContain('add_comment_as_supervisor');\n  228 |     expect(html).toContain('Recce comment');\n> 229 |     expect(html).toContain('💬 Comments');\n      |                  ^\n  230 |   });\n  231 |\n  232 |   test('Phase 4.18 — Sessions modal references pending-invite admin RPC', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 18,
                            "line": 229
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"💬 Comments\"\u001b[39m\nReceived string:    \u001b[31m\"<!DOCTYPE html>\u001b[39m\n\u001b[31m<html lang=\\\"en\\\">\u001b[39m\n\u001b[31m<head>\u001b[39m\n\u001b[31m<meta charset=\\\"UTF-8\\\">\u001b[39m\n\u001b[31m<meta name=\\\"viewport\\\" content=\\\"width=device-width,initial-scale=1,viewport-fit=cover\\\">\u001b[39m\n\u001b[31m<title>Counters</title>\u001b[39m\n\u001b[31m<link rel=\\\"manifest\\\" href=\\\"manifest.json\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"/shared/safe-bottom.css\\\">\u001b[39m\n\u001b[31m<script src=\\\"/shared/tour.js\\\"></script>\u001b[39m\n\u001b[31m<meta name=\\\"theme-color\\\" content=\\\"#0a0a0a\\\">\u001b[39m\n\u001b[31m<link rel=\\\"preconnect\\\" href=\\\"https://fonts.googleapis.com\\\">\u001b[39m\n\u001b[31m<link rel=\\\"stylesheet\\\" href=\\\"https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700;800&display=swap\\\">\u001b[39m\n\u001b[31m<style>\u001b[39m\n\u001b[31m:root{\u001b[39m\n\u001b[31m  --bg:#0a0a0a;--card:#141414;--card2:#1a1a1a;--border:#262626;--text:#f5f5f5;--muted:#a3a3a3;\u001b[39m\n\u001b[31m  --accent:#ec4899;--accent2:#db2777;--g:#22c55e;--r:#ef4444;--y:#eab308;--b:#3b82f6;--p:#a855f7;\u001b[39m\n\u001b[31m  --rad:12px;--rads:8px;--bnav-h:62px;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m*{box-sizing:border-box;margin:0;padding:0;-webkit-tap-highlight-color:transparent}\u001b[39m\n\u001b[31mbody{background:var(--bg);color:var(--text);font-family:'DM Sans',sans-serif;min-height:100vh}\u001b[39m\n\u001b[31mbutton{font-family:inherit;cursor:pointer;border:none;background:none;color:inherit}\u001b[39m\n\u001b[31ma{color:inherit;text-decoration:none}·\u001b[39m\n\u001b[31m.scr{display:none;flex-direction:column;min-height:100vh}\u001b[39m\n\u001b[31m.scr.active{display:flex}·\u001b[39m\n\u001b[31m.hdr{background:var(--card);border-bottom:1px solid var(--border);padding:12px 14px;display:flex;align-items:center;gap:8px;position:sticky;top:0;z-index:10}\u001b[39m\n\u001b[31m.hdr h1{font-size:16px;font-weight:800;flex:1;min-width:0}\u001b[39m\n\u001b[31m.hdr-sub{font-size:11px;color:var(--muted)}\u001b[39m\n\u001b[31m.hdr-btn{background:rgba(255,255,255,.08);border:1px solid var(--border);font-size:12px;font-weight:700;padding:5px 10px;border-radius:14px;cursor:pointer;white-space:nowrap}\u001b[39m\n\u001b[31m.hdr-btn:active{background:rgba(255,255,255,.16)}\u001b[39m\n\u001b[31m.hdr-btn.p{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.back-btn{background:none;font-size:20px;font-weight:900;padding:4px 6px;color:var(--accent)}\u001b[39m\n\u001b[31m.user-pill{background:rgba(255,255,255,.12);border:1px solid var(--border);color:var(--muted);font-size:10.5px;font-weight:600;padding:3px 9px;border-radius:12px;white-space:nowrap}\u001b[39m\n\u001b[31m.user-pill b{color:var(--text)}\u001b[39m\n\u001b[31m.role-tag{background:rgba(236,72,153,.15);color:#f9a8d4;font-size:9.5px;font-weight:800;padding:2px 6px;border-radius:8px;margin-left:4px;letter-spacing:.3px}·\u001b[39m\n\u001b[31m.body{flex:1;padding:14px;display:flex;flex-direction:column;gap:12px}·\u001b[39m\n\u001b[31m.tiles{display:grid;grid-template-columns:repeat(5,1fr);gap:6px}\u001b[39m\n\u001b[31m.tile{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:10px 5px;cursor:pointer;text-align:center}\u001b[39m\n\u001b[31m.tile.sel{border-color:var(--accent);background:linear-gradient(135deg,#3a0f29,#1c0814)}\u001b[39m\n\u001b[31m.tile-lbl{font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;font-weight:700}\u001b[39m\n\u001b[31m.tile-val{font-size:16px;font-weight:800;margin-top:3px}·\u001b[39m\n\u001b[31m.filter-chip{padding:4px 10px;border-radius:20px;border:1px solid var(--border);background:var(--card);color:var(--muted);font-size:.73rem;white-space:nowrap;cursor:pointer;font-family:inherit}\u001b[39m\n\u001b[31m.epip{width:20px;height:6px;border-radius:3px;background:var(--border);transition:background .2s;flex-shrink:0}\u001b[39m\n\u001b[31m.epip.done{background:var(--g)}.epip.active{background:var(--accent)}\u001b[39m\n\u001b[31m.mbadge{display:inline-block;font-size:9px;font-weight:700;letter-spacing:.4px;padding:2px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mbadge.exact{background:#14532d;color:#86efac}.mbadge.fuzzy{background:#713f12;color:#fde68a}.mbadge.newc{background:#1e3a5f;color:#93c5fd}\u001b[39m\n\u001b[31m.enrich-diff-row{display:flex;align-items:baseline;gap:6px;font-size:11px;padding:2px 0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-label{color:var(--muted);font-size:10px;min-width:70px;flex-shrink:0}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-old{color:var(--muted);text-decoration:line-through}\u001b[39m\n\u001b[31m.enrich-diff-row .ef-new{color:var(--g);font-weight:600}\u001b[39m\n\u001b[31m.filter-chip.active{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.filter-chip.unavailable{opacity:.32;pointer-events:none}\u001b[39m\n\u001b[31m.filter-rows-wrap{display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.filter-section{}\u001b[39m\n\u001b[31m.filter-label{font-size:.62rem;color:var(--muted);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}\u001b[39m\n\u001b[31m.filter-row{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px}\u001b[39m\n\u001b[31m.sum-tiles{display:grid;grid-template-columns:repeat(4,1fr);gap:5px}\u001b[39m\n\u001b[31m.sum-tile{background:var(--card);border:1px solid var(--border);border-radius:8px;padding:7px 4px;text-align:center;line-height:1.3}·\u001b[39m\n\u001b[31m.search-wrap{position:relative}\u001b[39m\n\u001b[31m.search-wrap input{width:100%;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--text);font-size:14px;padding:10px 12px 10px 38px;outline:none}\u001b[39m\n\u001b[31m.search-wrap input:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.search-wrap .icon{position:absolute;left:12px;top:50%;transform:translateY(-50%);color:var(--muted);font-size:16px}·\u001b[39m\n\u001b[31m.sec-label{font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;margin:6px 0 4px;display:flex;align-items:center;justify-content:space-between}·\u001b[39m\n\u001b[31m.c-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:6px;cursor:pointer}\u001b[39m\n\u001b[31m.c-row:active{border-color:var(--accent)}\u001b[39m\n\u001b[31m.c-row-top{display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.c-row-name{font-size:14px;font-weight:700;flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.c-row-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.c-row-meta .brands{display:flex;gap:4px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.brand-chip{font-size:9.5px;font-weight:700;padding:1px 7px;border-radius:10px;background:rgba(236,72,153,.18);color:#f9a8d4}\u001b[39m\n\u001b[31m.status-chip{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.status-chip.active{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.status-chip.inactive{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.status-chip.blacklisted{background:#7c2d12;color:#fca5a5}\u001b[39m\n\u001b[31m.status-chip.pending_review{background:#713f12;color:#fde68a}·\u001b[39m\n\u001b[31m.form-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:14px;display:grid;gap:10px}\u001b[39m\n\u001b[31m.form-grid{display:grid;grid-template-columns:1fr;gap:10px}\u001b[39m\n\u001b[31m@media (min-width:520px){.form-grid{grid-template-columns:1fr 1fr}}\u001b[39m\n\u001b[31m.field{display:flex;flex-direction:column;gap:3px}\u001b[39m\n\u001b[31m.field label{font-size:11px;font-weight:600;color:var(--muted)}\u001b[39m\n\u001b[31m.field label .req{color:var(--r)}\u001b[39m\n\u001b[31m.field input,.field textarea,.field select{background:#0b1220;border:1px solid var(--border);border-radius:6px;color:var(--text);padding:9px 11px;font-size:13px;font-family:inherit;outline:none}\u001b[39m\n\u001b[31m.field input:focus,.field textarea:focus,.field select:focus{border-color:var(--accent)}\u001b[39m\n\u001b[31m.field .hint{font-size:10px;color:var(--muted)}\u001b[39m\n\u001b[31m.field textarea{min-height:60px;resize:vertical}\u001b[39m\n\u001b[31m.full{grid-column:1/-1}·\u001b[39m\n\u001b[31m.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;border-radius:var(--rads);padding:11px 14px;font-size:14px;font-weight:700;cursor:pointer;width:100%}\u001b[39m\n\u001b[31m.btn-p{background:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.btn-p:active{background:var(--accent2)}\u001b[39m\n\u001b[31m.btn-p:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.btn-s{background:var(--card);color:var(--text);border:1px solid var(--border)}\u001b[39m\n\u001b[31m.btn-r{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.35)}\u001b[39m\n\u001b[31m.btn-row{display:grid;grid-template-columns:1fr 1fr;gap:8px}·\u001b[39m\n\u001b[31m.k-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:6px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.k-row .k-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.k-title{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.k-meta{font-size:11px;color:var(--muted);margin-top:2px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.k-badge{font-size:9px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.k-badge.primary,.k-badge.default{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.k-badge.owner{background:#581c87;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.manager{background:#082f49;color:#7dd3fc}\u001b[39m\n\u001b[31m.k-badge.accountant{background:#365314;color:#bef264}\u001b[39m\n\u001b[31m.k-badge.billing{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.k-badge.shipping{background:#1e3a8a;color:#93c5fd}\u001b[39m\n\u001b[31m.k-badge.site{background:#3b0764;color:#d8b4fe}\u001b[39m\n\u001b[31m.k-badge.warehouse{background:#374151;color:#d1d5db}\u001b[39m\n\u001b[31m.k-badge.other{background:#374151;color:#9ca3af}\u001b[39m\n\u001b[31m.k-actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.k-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:11px;font-weight:700;padding:4px 8px;border-radius:6px;cursor:pointer}·\u001b[39m\n\u001b[31m.tabs{display:flex;gap:0;border-bottom:1px solid var(--border);margin-bottom:8px;overflow-x:auto}\u001b[39m\n\u001b[31m.tabs button{flex:1;padding:9px 8px;font-size:12px;font-weight:700;color:var(--muted);border-bottom:2px solid transparent;background:none;white-space:nowrap}\u001b[39m\n\u001b[31m.tabs button.sel{color:var(--accent);border-bottom-color:var(--accent)}·\u001b[39m\n\u001b[31m.empty{text-align:center;padding:32px 16px;color:var(--muted)}\u001b[39m\n\u001b[31m.empty-icon{font-size:36px;margin-bottom:8px}·\u001b[39m\n\u001b[31m/* dedup suggestions during new-counter create */\u001b[39m\n\u001b[31m.dedup-panel{background:#221013;border:1px solid #7c2d12;border-radius:var(--rads);padding:9px 11px;font-size:11.5px;color:#fdba74;display:none}\u001b[39m\n\u001b[31m.dedup-panel.show{display:block}\u001b[39m\n\u001b[31m.dedup-panel .ttl{font-weight:800;margin-bottom:4px}\u001b[39m\n\u001b[31m.dedup-panel button{background:rgba(255,255,255,.08);color:#fdba74;border:1px solid #7c2d12;border-radius:6px;padding:4px 8px;font-size:11px;font-weight:700;margin-top:3px;margin-right:5px}·\u001b[39m\n\u001b[31m/* brand chips editor */\u001b[39m\n\u001b[31m.bcset{display:flex;flex-wrap:wrap;gap:6px;align-items:center;background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;min-height:42px}\u001b[39m\n\u001b[31m.bcset .b{background:rgba(236,72,153,.2);color:#f9a8d4;font-size:11.5px;font-weight:700;padding:3px 8px;border-radius:12px;display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.bcset .b button{background:none;color:inherit;font-size:13px;line-height:1;padding:0 2px;cursor:pointer;font-weight:800}\u001b[39m\n\u001b[31m.bcset input{background:none;border:none;color:var(--text);font-size:13px;padding:4px;flex:1;min-width:80px;outline:none}·\u001b[39m\n\u001b[31m/* modal for contact / address */\u001b[39m\n\u001b[31m.modal{position:fixed;inset:0;background:rgba(0,0,0,.75);z-index:200;display:none;align-items:flex-end;justify-content:center}\u001b[39m\n\u001b[31m.modal.show{display:flex}\u001b[39m\n\u001b[31m.modal-box{background:var(--card);border-radius:18px 18px 0 0;width:100%;max-width:560px;max-height:88vh;overflow-y:auto;padding:16px 14px calc(16px + env(safe-area-inset-bottom));border-top:1px solid var(--border)}\u001b[39m\n\u001b[31m.modal-hdr{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}\u001b[39m\n\u001b[31m.modal-ttl{font-size:15px;font-weight:800}\u001b[39m\n\u001b[31m.modal-close{font-size:24px;color:var(--muted);background:none;border:none;cursor:pointer}·\u001b[39m\n\u001b[31m/* toast */\u001b[39m\n\u001b[31m#toast{position:fixed;bottom:80px;left:50%;transform:translateX(-50%);background:#1f2937;color:#fff;padding:10px 16px;border-radius:18px;font-size:13px;font-weight:600;box-shadow:0 4px 12px rgba(0,0,0,.5);opacity:0;transition:.2s;z-index:300;pointer-events:none;max-width:90vw;text-align:center}\u001b[39m\n\u001b[31m#toast.show{opacity:1}\u001b[39m\n\u001b[31m#toast.ok{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m#toast.err{background:#7f1d1d;color:#fca5a5}·\u001b[39m\n\u001b[31m/* loader */\u001b[39m\n\u001b[31m.spin{display:inline-block;animation:spin 1s linear infinite}\u001b[39m\n\u001b[31m@keyframes spin{from{transform:rotate(0)}to{transform:rotate(360deg)}}·\u001b[39m\n\u001b[31m/* fixed bottom action bar inside detail/edit */\u001b[39m\n\u001b[31m.bnav{position:fixed;bottom:0;left:0;right:0;background:var(--card);border-top:1px solid var(--border);padding:10px 14px calc(10px + env(safe-area-inset-bottom));display:flex;gap:8px;z-index:50}\u001b[39m\n\u001b[31m.bnav .btn{padding:10px 12px;font-size:13px}·\u001b[39m\n\u001b[31m/* update banner */\u001b[39m\n\u001b[31m.upd-banner{display:none;background:#082f49;border-bottom:1px solid #0c4a6e;padding:7px 13px;font-size:12px;color:#7dd3fc;text-align:center;cursor:pointer}\u001b[39m\n\u001b[31m.upd-banner.show{display:block}\u001b[39m\n\u001b[31m.alloc-warn-banner{display:none;background:#431407;border-bottom:2px solid #c2410c;padding:8px 13px;font-size:12px;color:#fed7aa;align-items:center;gap:8px;justify-content:space-between}\u001b[39m\n\u001b[31m.alloc-warn-banner.show{display:flex}\u001b[39m\n\u001b[31m.alloc-warn-banner button{background:rgba(249,115,22,.25);border:1px solid rgba(249,115,22,.4);color:#fed7aa;font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0}·\u001b[39m\n\u001b[31m/* Phase 2: allocation modal employee tiles */\u001b[39m\n\u001b[31m.alloc-tiles{display:grid;grid-template-columns:repeat(2,1fr);gap:6px}\u001b[39m\n\u001b[31m.alloc-tile{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:9px 11px;cursor:pointer;font-size:12px;display:flex;align-items:center;gap:8px;transition:.12s}\u001b[39m\n\u001b[31m.alloc-tile:active{transform:scale(.98)}\u001b[39m\n\u001b[31m.alloc-tile.sel{background:rgba(34,197,94,.18);border-color:#22c55e;color:#86efac}\u001b[39m\n\u001b[31m.alloc-tile .cb{font-size:14px;line-height:1}\u001b[39m\n\u001b[31m.alloc-tile .nm{font-weight:700;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile .ro{font-size:10px;color:var(--muted);margin-top:1px;line-height:1.2}\u001b[39m\n\u001b[31m.alloc-tile.sel .ro{color:#86efac;opacity:.7}\u001b[39m\n\u001b[31m.alloc-tile.all-tile{grid-column:1/-1;background:linear-gradient(135deg,#0c2e3a,#062a3a);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.alloc-tile.all-tile.sel{background:linear-gradient(135deg,#3a0f29,#1c0814);border-color:#22c55e}\u001b[39m\n\u001b[31m.alloc-summary{font-size:11.5px;color:var(--muted);padding:6px 0;border-bottom:1px solid var(--border);margin-bottom:8px}·\u001b[39m\n\u001b[31m/* Phase 2: upload queue rows */\u001b[39m\n\u001b[31m.upload-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.upload-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.upload-row .ur-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.upload-row .ur-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-row .ur-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.upload-row .ur-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.upload-row.expanded .ur-body{display:block}\u001b[39m\n\u001b[31m.upload-rows-table{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:7px;font-size:11px;max-height:380px;overflow-y:auto;font-family:monospace;white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m.upload-match-hint{font-size:11.5px;background:rgba(34,197,94,.12);border-left:3px solid #22c55e;padding:6px 9px;border-radius:4px;color:#86efac;margin-top:6px}\u001b[39m\n\u001b[31m.upload-match-hint.partial{background:rgba(234,179,8,.12);border-left-color:#eab308;color:#fde68a}\u001b[39m\n\u001b[31m.upload-conflicts{margin-top:5px;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.25);border-radius:5px;padding:6px 8px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-hdr{font-size:10px;color:#fb923c;font-weight:700;margin-bottom:4px;letter-spacing:.2px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row{display:flex;align-items:flex-start;gap:7px;padding:3px 0;border-bottom:1px solid rgba(255,255,255,.04);cursor:pointer}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row:last-child{border-bottom:none}\u001b[39m\n\u001b[31m.upload-conflicts .uc-row input[type=checkbox]{margin-top:1px;accent-color:#fb923c;cursor:pointer;flex-shrink:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-content{flex:1;min-width:0}\u001b[39m\n\u001b[31m.upload-conflicts .uc-label{font-size:10px;font-weight:700;color:#94a3b8;text-transform:uppercase;letter-spacing:.3px;display:block}\u001b[39m\n\u001b[31m.upload-conflicts .uc-vals{display:flex;gap:6px;flex-wrap:wrap;align-items:center;margin-top:1px}\u001b[39m\n\u001b[31m.upload-conflicts .uc-old{font-size:10px;color:#6b7280;text-decoration:line-through}\u001b[39m\n\u001b[31m.upload-conflicts .uc-new{font-size:10px;color:#fb923c;font-weight:600}·\u001b[39m\n\u001b[31m/* Phase 3: Multi-Recce review */\u001b[39m\n\u001b[31m.mrg-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:7px}\u001b[39m\n\u001b[31m.mrg-row.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-row .mrg-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.mrg-row .mrg-name{font-size:14px;font-weight:700;flex:1;min-width:0}\u001b[39m\n\u001b[31m.mrg-row .mrg-meta{font-size:11px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-row .mrg-body{margin-top:12px;display:none}\u001b[39m\n\u001b[31m.mrg-row.expanded .mrg-body{display:block}\u001b[39m\n\u001b[31m.mrg-pick{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;text-transform:uppercase}\u001b[39m\n\u001b[31m.mrg-pick.has{background:#14532d;color:#86efac}\u001b[39m\n\u001b[31m.mrg-pick.none{background:#7c2d12;color:#fdba74}\u001b[39m\n\u001b[31m.recce-card{background:#0b1220;border:1px solid var(--border);border-radius:var(--rads);padding:10px 12px;margin-bottom:7px;display:flex;flex-direction:column;gap:5px}\u001b[39m\n\u001b[31m.recce-card.official{border-color:#22c55e;background:linear-gradient(135deg,#0c2e1a,#062a14)}\u001b[39m\n\u001b[31m.recce-card .rc-top{display:flex;align-items:center;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.recce-card .rc-date{font-size:13px;font-weight:700}\u001b[39m\n\u001b[31m.recce-card .rc-meta{font-size:11px;color:var(--muted);display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-card .rc-actions{display:flex;gap:6px;margin-top:6px}\u001b[39m\n\u001b[31m.recce-card .rc-actions button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:12px;font-weight:700;padding:7px 10px;border-radius:6px;cursor:pointer}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-pick:disabled{opacity:.45;cursor:default}\u001b[39m\n\u001b[31m.rc-view{padding:4px 10px;border-radius:6px;border:1px solid var(--border);font-size:11px;cursor:pointer;font-family:inherit;background:var(--card);color:var(--text)}\u001b[39m\n\u001b[31m.recce-card .rc-actions .rc-view{background:rgba(125,211,252,.1);color:#7dd3fc;border-color:rgba(125,211,252,.3)}\u001b[39m\n\u001b[31m.recce-card .rc-official-badge{font-size:9.5px;font-weight:800;padding:1px 7px;border-radius:10px;background:#14532d;color:#86efac;text-transform:uppercase}·\u001b[39m\n\u001b[31m/* Multi-Recce conflict resolver */\u001b[39m\n\u001b[31m.mrg-ab-tabs{display:flex;gap:0;margin-bottom:8px}\u001b[39m\n\u001b[31m.mrg-ab-tab{flex:1;padding:6px 8px;border:1px solid var(--border);background:transparent;color:var(--muted);font-size:11px;font-weight:700;cursor:pointer;border-radius:0;font-family:inherit}\u001b[39m\n\u001b[31m.mrg-ab-tab:first-child{border-radius:var(--rads) 0 0 var(--rads)}\u001b[39m\n\u001b[31m.mrg-ab-tab:last-child{border-radius:0 var(--rads) var(--rads) 0;border-left:none}\u001b[39m\n\u001b[31m.mrg-ab-tab.active{background:var(--accent);color:#fff;border-color:var(--accent)}\u001b[39m\n\u001b[31m.mrg-resolve-bar{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.mrg-resolve-bar button{flex:1;min-width:100px;padding:8px 8px;font-size:11.5px;font-weight:700;border-radius:var(--rads);cursor:pointer;font-family:inherit;border:1px solid var(--border)}\u001b[39m\n\u001b[31m.mrg-override-panel{background:#0b1220;border:1px solid rgba(251,191,36,.3);border-radius:var(--rads);padding:12px;margin-top:8px;display:none}\u001b[39m\n\u001b[31m.mrg-override-panel.open{display:block}\u001b[39m\n\u001b[31m.diff-table{width:100%;border-collapse:collapse;font-size:11.5px;margin-top:4px}\u001b[39m\n\u001b[31m.diff-table th{padding:5px 8px;background:var(--bg);color:var(--muted);text-align:left;font-size:10px;text-transform:uppercase;border-bottom:1px solid var(--border)}\u001b[39m\n\u001b[31m.diff-table td{padding:5px 8px;border-bottom:1px solid rgba(255,255,255,.04);vertical-align:top;max-width:160px;word-break:break-word}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(1){color:var(--muted)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(2){color:#fde68a;background:rgba(251,191,36,.06)}\u001b[39m\n\u001b[31m.diff-table tr.diff-row td:nth-child(3){color:#93c5fd;background:rgba(147,197,253,.06)}\u001b[39m\n\u001b[31m/* Past Recces tab filter */\u001b[39m\n\u001b[31m.recce-filter-bar{display:flex;flex-direction:column;gap:5px;margin-bottom:10px;padding:8px 10px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads)}\u001b[39m\n\u001b[31m.recce-filter-chips{display:flex;gap:5px;overflow-x:auto;padding-bottom:2px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.recce-date-row{display:flex;gap:6px;align-items:center}\u001b[39m\n\u001b[31m.recce-date-row input[type=date]{background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:11.5px;padding:3px 7px;flex:1;min-width:0}\u001b[39m\n\u001b[31m.recce-date-row label{font-size:11px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m/* Phase 4.5 — Clients admin */\u001b[39m\n\u001b[31m.cli-card{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:11px 13px;margin-bottom:8px}\u001b[39m\n\u001b[31m.cli-card.expanded{border-color:var(--accent)}\u001b[39m\n\u001b[31m.cli-top{display:flex;align-items:center;gap:8px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-name{flex:1;font-size:14px;font-weight:700;min-width:0}\u001b[39m\n\u001b[31m.cli-name .name{display:block}\u001b[39m\n\u001b[31m.cli-name .meta{display:flex;gap:6px;flex-wrap:wrap;margin-top:3px}\u001b[39m\n\u001b[31m.cli-meta-chip{font-size:10px;font-weight:700;background:rgba(236,72,153,.16);color:#f9a8d4;padding:1px 7px;border-radius:9px}\u001b[39m\n\u001b[31m.cli-meta-chip.users{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.cli-meta-chip.pending{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.cli-meta-chip.inactive{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.cli-body{margin-top:10px;display:none}\u001b[39m\n\u001b[31m.cli-card.expanded .cli-body{display:block}\u001b[39m\n\u001b[31m.cli-section-title{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:8px 0 4px}\u001b[39m\n\u001b[31m.cli-user{background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px}\u001b[39m\n\u001b[31m.cli-user.revoked{opacity:.55}\u001b[39m\n\u001b[31m.cli-user .u-main{flex:1;min-width:0}\u001b[39m\n\u001b[31m.cli-user .u-email{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.cli-user .u-meta{font-size:10.5px;color:var(--muted);margin-top:1px;display:flex;gap:6px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.cli-user .role{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;background:rgba(236,72,153,.18);color:#f9a8d4;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .scope{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.cli-user .actions{display:flex;gap:4px;flex-shrink:0}\u001b[39m\n\u001b[31m.cli-user .actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-user .actions .danger{background:rgba(239,68,68,.16);color:#fca5a5;border-color:rgba(239,68,68,.3)}\u001b[39m\n\u001b[31m.cli-invite{background:rgba(234,179,8,.08);border:1px dashed rgba(234,179,8,.4);border-radius:6px;padding:8px 10px;margin-bottom:5px;display:flex;align-items:center;gap:8px;font-size:11.5px;color:#fde68a}\u001b[39m\n\u001b[31m.cli-invite .actions{margin-left:auto}\u001b[39m\n\u001b[31m.cli-invite .actions button{background:rgba(239,68,68,.15);color:#fca5a5;border:1px solid rgba(239,68,68,.3);font-size:10.5px;font-weight:700;padding:4px 8px;border-radius:5px;cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-top:10px}\u001b[39m\n\u001b[31m.cli-row-actions button{font-size:12px;font-weight:700;padding:9px;border-radius:6px;border:1px solid var(--border);background:var(--card);color:var(--text);cursor:pointer}\u001b[39m\n\u001b[31m.cli-row-actions .invite{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.6 — Notifications */\u001b[39m\n\u001b[31m.notify-tabs{display:flex;gap:6px;margin:8px 0 10px}\u001b[39m\n\u001b[31m.notify-tabs button{flex:1;background:var(--card);border:1px solid var(--border);color:var(--muted);font-size:12px;font-weight:700;padding:7px;border-radius:7px;cursor:pointer}\u001b[39m\n\u001b[31m.notify-tabs button.on{background:var(--accent);border-color:var(--accent);color:#fff}\u001b[39m\n\u001b[31m.notify-card{background:var(--card);border:1px solid var(--border);border-left:3px solid var(--muted);border-radius:var(--rad);padding:10px 12px;margin-bottom:8px;font-size:12.5px}\u001b[39m\n\u001b[31m.notify-card.unseen{border-left-color:var(--accent);background:rgba(124,58,237,.06)}\u001b[39m\n\u001b[31m.notify-card .nf-top{display:flex;align-items:center;gap:7px;margin-bottom:4px}\u001b[39m\n\u001b[31m.notify-card .nf-icon{font-size:15px}\u001b[39m\n\u001b[31m.notify-card .nf-title{flex:1;font-weight:800;min-width:0}\u001b[39m\n\u001b[31m.notify-card .nf-when{font-size:10.5px;color:var(--muted);white-space:nowrap}\u001b[39m\n\u001b[31m.notify-card .nf-body{font-size:11.5px;color:var(--text);margin:2px 0 6px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.notify-card .nf-chips{display:flex;gap:5px;flex-wrap:wrap;margin-bottom:5px}\u001b[39m\n\u001b[31m.notify-card .nf-chip{font-size:9.5px;font-weight:700;padding:1px 6px;border-radius:7px;background:rgba(125,211,252,.16);color:#7dd3fc;text-transform:uppercase;letter-spacing:.3px}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-official{background:rgba(34,197,94,.18);color:#86efac}\u001b[39m\n\u001b[31m.notify-card .nf-chip.evt-response{background:rgba(236,72,153,.16);color:#f9a8d4}\u001b[39m\n\u001b[31m.notify-card .nf-chip.status-suppressed{background:rgba(107,114,128,.2);color:#9ca3af}\u001b[39m\n\u001b[31m.notify-card .nf-actions{display:flex;justify-content:flex-end;gap:6px}\u001b[39m\n\u001b[31m.notify-card .nf-actions button{background:rgba(255,255,255,.05);border:1px solid var(--border);font-size:10.5px;font-weight:700;padding:4px 10px;border-radius:5px;cursor:pointer;color:var(--text)}\u001b[39m\n\u001b[31m.notify-card .nf-actions .seen{background:var(--accent);border-color:var(--accent);color:#fff}·\u001b[39m\n\u001b[31m/* Phase 4.7 — Audit log */\u001b[39m\n\u001b[31m.audit-filters{display:flex;gap:6px;margin:8px 0 10px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.audit-filters select,.audit-filters input{flex:1;min-width:120px;background:var(--card);border:1px solid var(--border);color:var(--text);font-size:12px;padding:7px 9px;border-radius:7px}\u001b[39m\n\u001b[31m.audit-row{background:var(--card);border:1px solid var(--border);border-radius:var(--rad);padding:9px 12px;margin-bottom:6px;font-size:12px}\u001b[39m\n\u001b[31m.audit-row .au-top{display:flex;align-items:center;gap:7px;margin-bottom:3px}\u001b[39m\n\u001b[31m.audit-row .au-icon{font-size:14px}\u001b[39m\n\u001b[31m.audit-row .au-action{font-weight:800;text-transform:uppercase;letter-spacing:.3px;font-size:10.5px;color:var(--accent)}\u001b[39m\n\u001b[31m.audit-row .au-action.login{color:#7dd3fc}\u001b[39m\n\u001b[31m.audit-row .au-action.view{color:#86efac}\u001b[39m\n\u001b[31m.audit-row .au-action.response{color:#f9a8d4}\u001b[39m\n\u001b[31m.audit-row .au-when{font-size:10.5px;color:var(--muted);white-space:nowrap;margin-left:auto}\u001b[39m\n\u001b[31m.audit-row .au-user{font-size:12.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis}\u001b[39m\n\u001b[31m.audit-row .au-body{font-size:11.5px;color:var(--muted);margin-top:3px;line-height:1.4;word-break:break-word}\u001b[39m\n\u001b[31m.audit-row .au-body b{color:var(--text)}\u001b[39m\n\u001b[31m.audit-row .au-meta{font-size:10px;color:var(--muted);margin-top:3px;display:flex;gap:8px;flex-wrap:wrap}\u001b[39m\n\u001b[31m.au-risk{font-size:9.5px;font-weight:800;padding:1px 6px;border-radius:7px;text-transform:uppercase;letter-spacing:.3px;margin-left:6px}\u001b[39m\n\u001b[31m.au-risk.low{background:rgba(34,197,94,.16);color:#86efac}\u001b[39m\n\u001b[31m.au-risk.medium{background:rgba(234,179,8,.18);color:#fde68a}\u001b[39m\n\u001b[31m.au-risk.high{background:rgba(239,68,68,.2);color:#fca5a5}·\u001b[39m\n\u001b[31m/* Phase 4.14 — stats cards */\u001b[39m\n\u001b[31m.stat-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px}\u001b[39m\n\u001b[31m.stat-card{background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px 12px}\u001b[39m\n\u001b[31m.stat-card.full{grid-column:1 / -1}\u001b[39m\n\u001b[31m.stat-card h4{font-size:10.5px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;margin:0 0 6px}\u001b[39m\n\u001b[31m.stat-card .big{font-size:22px;font-weight:800;line-height:1}\u001b[39m\n\u001b[31m.stat-card .sub{font-size:10.5px;color:var(--muted);margin-top:3px}\u001b[39m\n\u001b[31m.bar{display:flex;height:8px;border-radius:4px;overflow:hidden;background:#0b1220;margin-top:6px}\u001b[39m\n\u001b[31m.bar > div{height:100%}\u001b[39m\n\u001b[31m.bar .low{background:#22c55e}\u001b[39m\n\u001b[31m.bar .medium{background:#eab308}\u001b[39m\n\u001b[31m.bar .high{background:#ef4444}\u001b[39m\n\u001b[31m.bar .approved{background:#22c55e}\u001b[39m\n\u001b[31m.bar .approved_with_changes{background:#eab308}\u001b[39m\n\u001b[31m.bar .rejected{background:#ef4444}\u001b[39m\n\u001b[31m.bar .pending{background:#6b7280}\u001b[39m\n\u001b[31m.legend{display:flex;flex-wrap:wrap;gap:6px;margin-top:6px;font-size:10.5px;color:var(--muted)}\u001b[39m\n\u001b[31m.legend span{display:inline-flex;align-items:center;gap:4px}\u001b[39m\n\u001b[31m.legend span::before{content:'';width:8px;height:8px;border-radius:2px;display:inline-block;background:currentColor;opacity:.5}\u001b[39m\n\u001b[31m.top-user{display:flex;align-items:center;gap:8px;padding:5px 0;border-top:1px solid var(--border);font-size:11.5px}\u001b[39m\n\u001b[31m.top-user:first-child{border-top:none}\u001b[39m\n\u001b[31m.top-user .tu-email{flex:1;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\u001b[39m\n\u001b[31m.top-user .tu-count{font-size:10.5px;color:var(--muted)}·\u001b[39m\n\u001b[31m/* Phase 4.17 — supervisor comment thread */\u001b[39m\n\u001b[31m.cmt-msg{display:flex;flex-direction:column;gap:3px;padding:8px 10px;border-radius:8px;font-size:12px;max-width:88%}\u001b[39m\n\u001b[31m.cmt-msg.client_user{background:rgba(124,58,237,.10);border:1px solid rgba(124,58,237,.3);align-self:flex-start}\u001b[39m\n\u001b[31m.cmt-msg.supervisor{background:rgba(34,197,94,.10);border:1px solid rgba(34,197,94,.3);align-self:flex-end}\u001b[39m\n\u001b[31m.cmt-msg .ch{font-size:10px;font-weight:800;color:var(--muted);text-transform:uppercase;letter-spacing:.3px;display:flex;justify-content:space-between;gap:8px}\u001b[39m\n\u001b[31m.cmt-msg .cb{font-size:12.5px;color:var(--text);white-space:pre-wrap;word-break:break-word}\u001b[39m\n\u001b[31m</style>\u001b[39m\n\u001b[31m</head>\u001b[39m\n\u001b[31m<body>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: LIST ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-list\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"upd-banner\\\" id=\\\"upd-banner\\\" onclick=\\\"reload()\\\">↻ New version available — tap to reload</div>\u001b[39m\n\u001b[31m  <div class=\\\"alloc-warn-banner\\\" id=\\\"alloc-warn-banner\\\">\u001b[39m\n\u001b[31m    <span id=\\\"alloc-warn-text\\\">⚠️ Inactive employees have allocated counters</span>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"filterByInactiveAlloc()\\\">View Counters</button>\u001b[39m\n\u001b[31m      <button onclick=\\\"document.getElementById('alloc-warn-banner').classList.remove('show')\\\" style=\\\"background:none;border:none;color:#fed7aa;font-size:16px;cursor:pointer;padding:0 2px\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <h1>🏪 Counters</h1>\u001b[39m\n\u001b[31m    <span class=\\\"user-pill\\\" id=\\\"user-pill\\\"></span>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/hub/'\\\">🏡 Hub</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"window.location.href='/learn/?pwa=counters'\\\" title=\\\"Tutorials\\\">🎓 Tutorials</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"logout()\\\" title=\\\"Log Out\\\">🔒</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-coverage\\\"   onclick=\\\"openCoverageReport()\\\" style=\\\"display:none\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-notify\\\"     onclick=\\\"openNotify()\\\"     style=\\\"display:none\\\">🔔 <span id=\\\"notify-badge\\\">Notify</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-audit\\\"      onclick=\\\"openAudit()\\\"      style=\\\"display:none\\\">📋 <span id=\\\"audit-badge\\\">Audit</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-clients\\\"    onclick=\\\"openClients()\\\"    style=\\\"display:none\\\">🏷 <span id=\\\"clients-badge\\\">Clients</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-multirecce\\\" onclick=\\\"openMultiRecce()\\\" style=\\\"display:none\\\">📌 <span id=\\\"multirecce-badge\\\">Multi-Recce</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-uploads\\\"    onclick=\\\"openUploads()\\\"    style=\\\"display:none\\\">📥 <span id=\\\"upload-badge\\\">Uploads</span></button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-sync-list\\\" onclick=\\\"loadList()\\\" title=\\\"Refresh counter list\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-tour\\\" onclick=\\\"window._tour&&window._tour.show()\\\" title=\\\"Supervisor guide\\\">? Help</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-bulk\\\"       onclick=\\\"openBulkEdit()\\\"  style=\\\"display:none\\\">⚡ Bulk</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-geofill\\\" onclick=\\\"openGeoFill()\\\" style=\\\"display:none\\\" title=\\\"AI suggests state_code and city_tier from city/state names\\\">✨ Geo-fill</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" id=\\\"btn-new\\\"       onclick=\\\"openNew()\\\"       style=\\\"display:none\\\">+ New</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"all\\\"        onclick=\\\"setTile(event,'all')\\\">       <div class=\\\"tile-lbl\\\">All</div>          <div class=\\\"tile-val\\\" id=\\\"t-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"active\\\"     onclick=\\\"setTile(event,'active')\\\">    <div class=\\\"tile-lbl\\\">Active</div>       <div class=\\\"tile-val\\\" id=\\\"t-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"pending\\\"    onclick=\\\"setTile(event,'pending')\\\">   <div class=\\\"tile-lbl\\\">Pending</div>      <div class=\\\"tile-val\\\" id=\\\"t-pending\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"norecce\\\"    onclick=\\\"setTile(event,'norecce')\\\">   <div class=\\\"tile-lbl\\\">No Recce</div>     <div class=\\\"tile-val\\\" id=\\\"t-norecce\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"stale90\\\"    onclick=\\\"setTile(event,'stale90')\\\">   <div class=\\\"tile-lbl\\\">Stale 90d</div>    <div class=\\\"tile-val\\\" id=\\\"t-stale90\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\" data-filter=\\\"inactive\\\"   onclick=\\\"setTile(event,'inactive')\\\">  <div class=\\\"tile-lbl\\\">Closed</div>       <div class=\\\"tile-val\\\" id=\\\"t-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"srch\\\" placeholder=\\\"Search name, GSTIN, city, pincode…\\\" oninput=\\\"renderList()\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"loadList()\\\" title=\\\"Refresh list\\\" style=\\\"background:none;border:none;font-size:16px;cursor:pointer;padding:0 4px;color:var(--muted)\\\">↻</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sum-tiles\\\" id=\\\"list-sum-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;padding:2px 0 4px\\\">\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-brand\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Brands</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-territory\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Territories</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"list-filter-rep\\\" onchange=\\\"onRepFilterChange()\\\" style=\\\"font-size:11px;padding:4px 8px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;flex:1;min-width:0;display:none\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All Reps</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-chip-rows\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters</span>\u001b[39m\n\u001b[31m      <span style=\\\"display:flex;align-items:center;gap:8px\\\">\u001b[39m\n\u001b[31m        <select id=\\\"list-sort\\\" onchange=\\\"renderList()\\\" style=\\\"font-size:11px;padding:2px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif\\\">\u001b[39m\n\u001b[31m          <option value=\\\"name\\\">A–Z</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_desc\\\">Most Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_asc\\\">Least Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"recent\\\">Recent Recce</option>\u001b[39m\n\u001b[31m          <option value=\\\"stale\\\">Stale first</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <span id=\\\"list-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m        <button onclick=\\\"exportCounterCSV()\\\" title=\\\"Export filtered list to CSV\\\" style=\\\"background:none;border:none;font-size:13px;cursor:pointer;padding:0 2px;color:var(--muted)\\\" id=\\\"btn-export\\\">⬇ CSV</button>\u001b[39m\n\u001b[31m      </span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"list-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: DETAIL ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-detail\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"d-title\\\">Counter</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-allocate\\\" onclick=\\\"openAllocate()\\\" style=\\\"display:none\\\">👥 Allocate</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-edit\\\"     onclick=\\\"openEdit()\\\"     style=\\\"display:none\\\">✏ Edit</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Identity</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-identity\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Address</div>\u001b[39m\n\u001b[31m      <div id=\\\"d-address\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\" id=\\\"d-notes\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"sel\\\" data-tab=\\\"brands\\\"   onclick=\\\"setTab('brands')\\\">Brands</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"contacts\\\" onclick=\\\"setTab('contacts')\\\">Contacts</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"addrs\\\"    onclick=\\\"setTab('addrs')\\\">More Addresses</button>\u001b[39m\n\u001b[31m      <button         data-tab=\\\"recces\\\"   onclick=\\\"setTab('recces')\\\">Past Recces</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"d-brands\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-contacts\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-addrs\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"d-recces\\\" style=\\\"display:none\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NEW / EDIT ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-form\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"cancelForm()\\\">‹</button>\u001b[39m\n\u001b[31m    <h1 id=\\\"f-title\\\">New Counter</h1>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\" style=\\\"padding-bottom:88px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"dedup-panel\\\" id=\\\"dedup\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ttl\\\">⚠ Possible existing counter</div>\u001b[39m\n\u001b[31m      <div id=\\\"dedup-list\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Display name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-display_name\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Legal name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-legal_name\\\" maxlength=\\\"180\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Proprietor</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-proprietor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Aliases <span class=\\\"hint\\\">(comma-separated)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-aliases\\\" placeholder=\\\"e.g. Saini Electronics, Saini Elect.\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Primary address</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line1\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Line 2</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-address_line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-city\\\" oninput=\\\"dedupCheck()\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>State code <span class=\\\"hint\\\">(2-digit GST)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-state_code\\\" maxlength=\\\"2\\\" pattern=\\\"[0-9]{2}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Pincode</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pincode\\\" oninput=\\\"dedupCheck();pincodeAutoFill(this.value)\\\" maxlength=\\\"6\\\" pattern=\\\"[0-9]{6}\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lat</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lat\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GPS lng</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gps_lng\\\" type=\\\"number\\\" step=\\\"0.0000001\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <button type=\\\"button\\\" onclick=\\\"fillGpsFromBrowser()\\\" style=\\\"font-size:12px;padding:6px 12px;border:1px solid var(--border);border-radius:6px;background:var(--card);cursor:pointer;color:var(--accent)\\\">📍 Use my current location</button>\u001b[39m\n\u001b[31m          <div id=\\\"gps-status\\\" style=\\\"font-size:11px;color:var(--muted);margin-top:4px\\\"></div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Tax / Legal</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>GSTIN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-gstin\\\" maxlength=\\\"15\\\" oninput=\\\"upper(this);dedupCheck()\\\" placeholder=\\\"15 chars\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>PAN</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-pan\\\" maxlength=\\\"10\\\" oninput=\\\"upper(this)\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>FSSAI</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-fssai\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Drug licence</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-drug_lic\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Commercial</div>\u001b[39m\n\u001b[31m      <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Counter type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-counter_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m            <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Channel</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-channel\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"general_trade\\\">General trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"institution\\\">Institution</option>\u001b[39m\n\u001b[31m            <option value=\\\"e_commerce\\\">E-commerce</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Customer group</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-customer_group\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Territory</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-territory\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Geo Cluster <span style=\\\"color:var(--muted);font-weight:400;font-size:10px\\\">(tricity, ncr…)</span></label>\u001b[39m\n\u001b[31m          <input id=\\\"f-geo_cluster\\\" maxlength=\\\"60\\\" placeholder=\\\"e.g. tricity, ncr, doaba\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>City Tier</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-city_tier\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"tier1\\\">Tier 1 (Metro)</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier2\\\">Tier 2</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier3\\\">Tier 3</option>\u001b[39m\n\u001b[31m            <option value=\\\"tier4\\\">Tier 4 (Rural)</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Store Type</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-store_type\\\">\u001b[39m\n\u001b[31m            <option value=\\\"\\\"></option>\u001b[39m\n\u001b[31m            <option value=\\\"les\\\">LES – Large Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"ses\\\">SES – Small Electronics Store</option>\u001b[39m\n\u001b[31m            <option value=\\\"mbo\\\">MBO – Multi-Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"ebo\\\">EBO – Exclusive Brand Outlet</option>\u001b[39m\n\u001b[31m            <option value=\\\"modern_trade\\\">Modern Trade</option>\u001b[39m\n\u001b[31m            <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Distributor name</label>\u001b[39m\n\u001b[31m          <input id=\\\"f-distributor_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field\\\">\u001b[39m\n\u001b[31m          <label>Status</label>\u001b[39m\n\u001b[31m          <select id=\\\"f-status\\\">\u001b[39m\n\u001b[31m            <option value=\\\"active\\\">Active</option>\u001b[39m\n\u001b[31m            <option value=\\\"inactive\\\">Inactive</option>\u001b[39m\n\u001b[31m            <option value=\\\"blacklisted\\\">Blacklisted</option>\u001b[39m\n\u001b[31m            <option value=\\\"pending_review\\\">Pending review</option>\u001b[39m\n\u001b[31m          </select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m          <label>Brands</label>\u001b[39m\n\u001b[31m          <div class=\\\"bcset\\\" id=\\\"bcset\\\" onclick=\\\"document.getElementById('brand-inp').focus()\\\">\u001b[39m\n\u001b[31m            <input id=\\\"brand-inp\\\" placeholder=\\\"Type brand + Enter\\\" onkeydown=\\\"brandKey(event)\\\">\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"form-card\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700\\\">Notes</div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <textarea id=\\\"f-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"cancelForm()\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-save\\\" onclick=\\\"saveForm()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: CLIENTS ADMIN (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-clients\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🏷 Clients</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadClients()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"openNewClient()\\\">+ Client</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"tiles\\\" id=\\\"cli-tiles\\\">\u001b[39m\n\u001b[31m      <div class=\\\"tile sel\\\" data-cfilter=\\\"all\\\"      onclick=\\\"setCFilter(event,'all')\\\">      <div class=\\\"tile-lbl\\\">All</div>       <div class=\\\"tile-val\\\" id=\\\"cf-all\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"active\\\"   onclick=\\\"setCFilter(event,'active')\\\">   <div class=\\\"tile-lbl\\\">Active</div>    <div class=\\\"tile-val\\\" id=\\\"cf-active\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"inactive\\\" onclick=\\\"setCFilter(event,'inactive')\\\"> <div class=\\\"tile-lbl\\\">Inactive</div>  <div class=\\\"tile-val\\\" id=\\\"cf-inactive\\\">—</div></div>\u001b[39m\n\u001b[31m      <div class=\\\"tile\\\"     data-cfilter=\\\"users\\\"    onclick=\\\"setCFilter(event,'users')\\\">    <div class=\\\"tile-lbl\\\">With users</div><div class=\\\"tile-val\\\" id=\\\"cf-users\\\">—</div></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"search-wrap\\\">\u001b[39m\n\u001b[31m      <span class=\\\"icon\\\">🔍</span>\u001b[39m\n\u001b[31m      <input id=\\\"cli-srch\\\" placeholder=\\\"Search name or brand code…\\\" oninput=\\\"renderClients()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Brand-side clients</span>\u001b[39m\n\u001b[31m      <span id=\\\"clients-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each client is a brand entity. Tap to expand and manage their users, invitations, role + scope. Users sign in at <span style=\\\"color:var(--accent)\\\">/recce-client/</span>.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"clients-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: NOTIFICATIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-notify\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>🔔 Notifications</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadNotify()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-mark-all-seen\\\" onclick=\\\"markAllNotifySeen()\\\">Mark all seen</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"notify-tabs\\\">\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-unseen\\\" class=\\\"on\\\" onclick=\\\"setNotifyTab('unseen')\\\">Unseen</button>\u001b[39m\n\u001b[31m      <button id=\\\"nf-tab-all\\\"           onclick=\\\"setNotifyTab('all')\\\">All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Events that warrant a supervisor's attention: Recces flipped to <span style=\\\"color:#86efac\\\">Official</span>, client <span style=\\\"color:#f9a8d4\\\">Approved / Changes-requested</span> responses, and other internal alerts. Emails to client_users are dispatched automatically.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"notify-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: AUDIT LOG (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-audit\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📋 Audit log</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadAudit()\\\">↻</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"openAuditStats()\\\">📊 Stats</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <select id=\\\"audit-action\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All actions</option>\u001b[39m\n\u001b[31m        <option value=\\\"login\\\">Logins</option>\u001b[39m\n\u001b[31m        <option value=\\\"recce_view\\\">Recce views</option>\u001b[39m\n\u001b[31m        <option value=\\\"response_submit\\\">Responses</option>\u001b[39m\n\u001b[31m        <option value=\\\"session_revoke\\\">Session revokes</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <select id=\\\"audit-risk\\\" onchange=\\\"renderAudit()\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">All risk levels</option>\u001b[39m\n\u001b[31m        <option value=\\\"high\\\">High risk only</option>\u001b[39m\n\u001b[31m        <option value=\\\"medium\\\">Medium+ risk</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m      <input id=\\\"audit-user\\\" placeholder=\\\"client_user_id (optional)\\\" oninput=\\\"loadAuditDebounced()\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"audit-filters\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-from\\\" type=\\\"date\\\" title=\\\"From date\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <input id=\\\"audit-to\\\"   type=\\\"date\\\" title=\\\"To date (exclusive)\\\" onchange=\\\"loadAudit()\\\">\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"exportAuditCsv()\\\">⤓ Export CSV</button>\u001b[39m\n\u001b[31m      <button class=\\\"hdr-btn\\\" onclick=\\\"pruneAuditNow()\\\">🧹 Prune &gt; 180d</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      Every client_user action on the Recce Portal — magic-link logins, Recce viewer opens, accept / reject / approved_with_changes responses. IP + UA captured for forensics.<br>\u001b[39m\n\u001b[31m      <span style=\\\"color:#fde68a\\\">Retention: rows older than 180 days are auto-pruned nightly by the mail relay worker.</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"audit-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: NEW CLIENT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-newclient\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">🏷 New client</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-newclient')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Client name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-name\\\" placeholder=\\\"e.g. HP India\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Brand codes <span class=\\\"hint\\\">(comma-separated; these gate Recce visibility)</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-brand_codes\\\" placeholder=\\\"HP, HP_INK\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact name</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Contact email</label>\u001b[39m\n\u001b[31m        <input id=\\\"ncf-contact_email\\\" type=\\\"email\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Notes</label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ncf-notes\\\" maxlength=\\\"2000\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-newclient')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveNewClient()\\\">Create client</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: INVITE USER ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-invite\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"invite-ttl\\\">✉ Invite user</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-invite')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Email <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-email\\\" type=\\\"email\\\" placeholder=\\\"user@brand.com\\\" maxlength=\\\"160\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Display name</label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-display_name\\\" placeholder=\\\"Optional; will use email if blank\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"Viewer\\\">Viewer</option>\u001b[39m\n\u001b[31m          <option value=\\\"SM\\\">SM</option>\u001b[39m\n\u001b[31m          <option value=\\\"ASM\\\">ASM</option>\u001b[39m\n\u001b[31m          <option value=\\\"Marketing\\\">Marketing</option>\u001b[39m\n\u001b[31m          <option value=\\\"Admin\\\">Admin</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Scope</label>\u001b[39m\n\u001b[31m        <select id=\\\"invf-scope_kind\\\">\u001b[39m\n\u001b[31m          <option value=\\\"all\\\">All client's Recces</option>\u001b[39m\n\u001b[31m          <option value=\\\"territory\\\">By territory</option>\u001b[39m\n\u001b[31m          <option value=\\\"job_id\\\">By job_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"recce_id\\\">By recce_id</option>\u001b[39m\n\u001b[31m          <option value=\\\"brand_subset\\\">Brand subset</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Scope values <span class=\\\"hint\\\">(comma-separated; ignored when scope = \\\"All\\\")</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"invf-scope_values\\\" placeholder=\\\"e.g. NORTH, WEST or JOB-100, JOB-101\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-invite')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveInvite()\\\">Send invitation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"margin-top:10px;font-size:11px;color:var(--muted);text-align:center\\\">\u001b[39m\n\u001b[31m      A magic-link email will be queued. Worker delivers within 30s.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: RECCE COMMENTS (Phase 4.17) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-comments\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">💬 Recce comments · <span id=\\\"comments-sub-label\\\"></span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-comments')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"comments-out\\\" style=\\\"display:flex;flex-direction:column;gap:8px;max-height:50vh;overflow-y:auto;padding:4px 0\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;flex-direction:column;gap:6px;margin-top:12px\\\">\u001b[39m\n\u001b[31m      <textarea id=\\\"comment-body\\\" maxlength=\\\"4000\\\" placeholder=\\\"Type a reply to send to the client…\\\" style=\\\"width:100%;background:#0b1220;border:1px solid var(--border);color:var(--text);font-size:12.5px;padding:8px 10px;border-radius:8px;min-height:60px;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-comments')\\\">Close</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"comment-post\\\" onclick=\\\"postSupervisorComment()\\\">Post reply</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AUDIT STATS (supervisor only, Phase 4.14) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-stats\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Audit stats · <span id=\\\"stats-window\\\">last 7 days</span></div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-stats')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"stats-out\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-stats')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATION COVERAGE REPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-coverage\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">📊 Allocation Coverage</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-coverage')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"coverage-out\\\" style=\\\"padding:0 2px;max-height:65vh;overflow-y:auto\\\"><div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-coverage')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK EDIT COUNTERS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-edit\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⚡ Bulk Edit Counters</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\">\u001b[39m\n\u001b[31m      Apply a change to all counters matching the <b>current list filter</b>. Only blank fields are filled; existing values are never overwritten — except brand codes which are merged additively.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-scope-line\\\" style=\\\"font-size:12px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:8px 12px;margin-bottom:14px;color:var(--muted)\\\">\u001b[39m\n\u001b[31m      Scope: <b id=\\\"bulk-scope-count\\\">—</b> counters selected by current filter\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\" style=\\\"gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Brand Code to Add</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-brand\\\" placeholder=\\\"e.g. HP\\\" maxlength=\\\"40\\\" oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Merged with existing brands — never removes existing codes</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Counter Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-counter-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"sub_dealer\\\">Sub-dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"dealer\\\">Dealer</option>\u001b[39m\n\u001b[31m          <option value=\\\"distributor\\\">Distributor</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">Modern trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"standalone\\\">Standalone</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no counter type set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Territory</label>\u001b[39m\n\u001b[31m        <input id=\\\"bulk-territory\\\" placeholder=\\\"e.g. North-HP\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no territory set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Channel</label>\u001b[39m\n\u001b[31m        <select id=\\\"bulk-channel\\\">\u001b[39m\n\u001b[31m          <option value=\\\"\\\">— don't change —</option>\u001b[39m\n\u001b[31m          <option value=\\\"general_trade\\\">general_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"modern_trade\\\">modern_trade</option>\u001b[39m\n\u001b[31m          <option value=\\\"institution\\\">institution</option>\u001b[39m\n\u001b[31m          <option value=\\\"e_commerce\\\">e_commerce</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:10.5px;color:var(--muted);margin-top:2px\\\">Only applied to counters with no channel set</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-status\\\" style=\\\"font-size:12px;min-height:18px;margin:10px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-edit')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-bulk-apply\\\" onclick=\\\"runBulkEdit()\\\">Apply to <span id=\\\"bulk-apply-count\\\">?</span> counters</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: USER SESSIONS (supervisor only) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-sessions\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"sess-ttl\\\">🔑 Sessions</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-sessions')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"sess-modal-out\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-sessions')\\\">Close</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: MULTI-RECCE REVIEW (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-multirecce\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📌 Multi-Recce Review</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadMultiRecce()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Counters with multiple Recces</span>\u001b[39m\n\u001b[31m      <span id=\\\"mrg-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);padding:8px 11px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);margin-bottom:6px\\\">\u001b[39m\n\u001b[31m      Each group has 2+ Recces for the same counter + campaign (usually due to reassignment).\u001b[39m\n\u001b[31m      Compare A vs B field-by-field, pick one as official, or use Supervisor Override to record\u001b[39m\n\u001b[31m      the reason for your decision. The unchosen Recce is marked superseded. Tap \\\"View Counter →\\\"\u001b[39m\n\u001b[31m      to see the full Recce history for any counter across all campaigns.\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"mrg-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── SCREEN: UPLOADS QUEUE (supervisor only) ── -->\u001b[39m\n\u001b[31m<div id=\\\"s-uploads\\\" class=\\\"scr\\\">\u001b[39m\n\u001b[31m  <div class=\\\"hdr\\\">\u001b[39m\n\u001b[31m    <button class=\\\"back-btn\\\" onclick=\\\"showScreen('s-list')\\\">‹</button>\u001b[39m\n\u001b[31m    <h1>📥 Counter Uploads</h1>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"downloadUploadTemplate()\\\" title=\\\"Download CSV template with all fields\\\">⬇ Template</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn p\\\" onclick=\\\"document.getElementById('upload-file-input').click()\\\" title=\\\"Upload CSV file\\\">⬆ Upload CSV</button>\u001b[39m\n\u001b[31m    <input type=\\\"file\\\" id=\\\"upload-file-input\\\" accept=\\\".csv\\\" style=\\\"display:none\\\" onchange=\\\"handleUploadFileSelect(this)\\\">\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" id=\\\"btn-ai-import\\\" onclick=\\\"openAiImport()\\\" title=\\\"Paste any store list — AI converts it to upload format\\\" style=\\\"display:none\\\">🤖 AI Import</button>\u001b[39m\n\u001b[31m    <button class=\\\"hdr-btn\\\" onclick=\\\"loadUploads()\\\">↻</button>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m  <div class=\\\"body\\\">\u001b[39m\n\u001b[31m    <div class=\\\"sec-label\\\">\u001b[39m\n\u001b[31m      <span>Awaiting supervisor review</span>\u001b[39m\n\u001b[31m      <span id=\\\"uploads-count\\\" style=\\\"font-weight:600;color:var(--muted);text-transform:none;letter-spacing:0;font-size:11px\\\"></span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"uploads-out\\\"></div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CSV UPLOAD ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-csvup\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:420px\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⬆ Upload Counter List</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-csvup')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>List Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-name\\\" placeholder=\\\"e.g. HP Punjab Q3 2026\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\">\u001b[39m\n\u001b[31m      <label>Job ID <span class=\\\"hint\\\">(optional — links upload to a specific campaign)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"csvup-job\\\" placeholder=\\\"leave blank for generic upload\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"csvup-preview\\\" style=\\\"font-size:12px;color:var(--muted);margin:8px 0;line-height:1.5\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-csvup')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"csvup-submit\\\" onclick=\\\"submitUploadRows()\\\">Submit for Review</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI IMPORT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-ai-import\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-width:580px;max-height:92vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"ai-import-ttl\\\">🤖 AI Store Import</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-ai-import')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 1+2: paste + AI convert (existing) -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p12\\\" style=\\\"display:flex;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:12px;color:var(--muted);margin-bottom:10px;line-height:1.5\\\">\u001b[39m\n\u001b[31m        Paste any raw store list — WhatsApp forward, typed list, Excel copy-paste, anything. AI converts it, then matches to existing counters and enriches geo fields.\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\" style=\\\"flex:1\\\">\u001b[39m\n\u001b[31m        <label>Raw Store List <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <textarea id=\\\"ai-raw-input\\\" rows=\\\"8\\\" placeholder=\\\"Paste your store list here…\u001b[39m\n\u001b[31mExample:\u001b[39m\n\u001b[31mGanesh Medical Store\u001b[39m\n\u001b[31mNear Bus Stand, Pune 411001\u001b[39m\n\u001b[31mOwner: Ramesh Patil, 9876543210·\u001b[39m\n\u001b[31mSharma Electronics, 45 MG Road, Bangalore 560001\u001b[39m\n\u001b[31mContact: Anita 9988776655\\\" style=\\\"width:100%;resize:vertical;font-size:12px;font-family:monospace;background:var(--bg);color:var(--text);border:1px solid var(--border);border-radius:6px;padding:8px;box-sizing:border-box\\\" oninput=\\\"aiDetectStores(this.value)\\\"></textarea>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-detect-hint\\\" style=\\\"font-size:11px;color:var(--muted);text-align:right;margin-top:2px;min-height:14px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-status\\\" style=\\\"font-size:12px;color:var(--muted);margin:6px 0;min-height:18px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"ai-import-preview\\\" style=\\\"display:none;margin-top:8px\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);text-transform:uppercase;letter-spacing:.05em;margin-bottom:6px\\\">\u001b[39m\n\u001b[31m          Converted — <span id=\\\"ai-preview-count\\\"></span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div id=\\\"ai-preview-table\\\" style=\\\"background:#0b1220;border:1px solid var(--border);border-radius:6px;padding:8px;font-size:11px;max-height:160px;overflow-y:auto;font-family:monospace\\\"></div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-ai-import')\\\">Cancel</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-ai-convert\\\" onclick=\\\"runAiConvert()\\\">Convert with AI</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-ai-use-rows\\\" onclick=\\\"startEnrichment()\\\" style=\\\"display:none\\\">Match &amp; Enrich →</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 3: matching + geo enrichment in progress -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p3\\\" style=\\\"display:none;flex-direction:column;align-items:center;justify-content:center;padding:30px 10px;gap:10px\\\">\u001b[39m\n\u001b[31m      <div class=\\\"spin\\\" style=\\\"font-size:28px\\\">⟳</div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-p3-status\\\" style=\\\"font-size:13px;color:var(--muted);text-align:center\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Phase 4: row-by-row guided review -->\u001b[39m\n\u001b[31m    <div id=\\\"enrich-p4\\\" style=\\\"display:none;flex-direction:column;flex:1;overflow:hidden\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">Review each store — accept enrichment, edit, or skip</div>\u001b[39m\n\u001b[31m      <!-- Pip bar -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pips\\\" style=\\\"display:flex;gap:3px;flex-wrap:wrap;margin-bottom:10px\\\"></div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-pip-label\\\" style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m      <!-- Review card -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-card\\\" style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:12px;overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m      <!-- Decision buttons -->\u001b[39m\n\u001b[31m      <div id=\\\"enrich-actions\\\" style=\\\"display:flex;gap:8px;margin-top:10px;flex-wrap:wrap;align-items:center\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-enrich-accept\\\" onclick=\\\"enrichDecide('accept')\\\" style=\\\"flex:1\\\">✓ Accept</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-skip\\\"   onclick=\\\"enrichDecide('skip')\\\"   style=\\\"flex:1\\\">✗ Skip</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-s\\\" id=\\\"btn-enrich-apply\\\"  onclick=\\\"applyEnrichment()\\\"      style=\\\"display:none;flex:2;font-weight:700\\\">Apply accepted changes</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"enrich-apply-status\\\" style=\\\"font-size:11px;color:var(--muted);min-height:16px;margin-top:6px\\\"></div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: BULK REASSIGN (inactive employees) ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-bulk-reassign\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">⇄ Bulk Reassign</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-from\\\" style=\\\"font-size:13px;color:var(--muted);margin-bottom:8px\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Reassign to</label>\u001b[39m\n\u001b[31m      <select id=\\\"bulk-reassign-to\\\" style=\\\"width:100%;padding:9px 12px;border:1px solid var(--border);border-radius:8px;background:var(--card);color:var(--text);font-family:'DM Sans',sans-serif;font-size:14px\\\">\u001b[39m\n\u001b[31m        <option value=\\\"\\\">— Select active employee —</option>\u001b[39m\n\u001b[31m      </select>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"bulk-reassign-summary\\\" style=\\\"font-size:12px;color:var(--muted);margin-bottom:12px\\\"></div>\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-bulk-reassign')\\\" style=\\\"flex:1\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"confirmBulkReassign()\\\" style=\\\"flex:2;background:#ea580c\\\" id=\\\"btn-do-bulk-reassign\\\">⇄ Reassign All</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ALLOCATE ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-alloc\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"alloc-ttl\\\">👥 Allocate counter</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-alloc')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-summary\\\" id=\\\"alloc-summary\\\">Loading employees…</div>\u001b[39m\n\u001b[31m    <div class=\\\"field\\\" style=\\\"margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <label>Optional Job ID <span class=\\\"hint\\\">(scope this allocation to one job)</span></label>\u001b[39m\n\u001b[31m      <input id=\\\"alloc-job_id\\\" placeholder=\\\"leave blank for all jobs\\\" maxlength=\\\"40\\\">\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tiles\\\" id=\\\"alloc-tiles\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-alloc')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAlloc()\\\">Apply allocation</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: CONTACT ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-contact\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"contact-ttl\\\">Add contact</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-contact')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Name <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-name\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Role</label>\u001b[39m\n\u001b[31m        <select id=\\\"cf-role\\\">\u001b[39m\n\u001b[31m          <option value=\\\"owner\\\">Owner</option>\u001b[39m\n\u001b[31m          <option value=\\\"manager\\\">Manager</option>\u001b[39m\n\u001b[31m          <option value=\\\"accountant\\\">Accountant</option>\u001b[39m\n\u001b[31m          <option value=\\\"primary\\\">Primary</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Designation</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-designation\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Phone</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-phone\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>WhatsApp</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-whatsapp\\\" maxlength=\\\"20\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Email</label>\u001b[39m\n\u001b[31m        <input id=\\\"cf-email\\\" maxlength=\\\"120\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"cf-is_primary\\\">\u001b[39m\n\u001b[31m          <span>Mark as primary contact</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-contact')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveContact()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<!-- ── MODAL: ADDRESS ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-address\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\" id=\\\"address-ttl\\\">Add address</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-address')\\\">×</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"form-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Type</label>\u001b[39m\n\u001b[31m        <select id=\\\"af-type\\\">\u001b[39m\n\u001b[31m          <option value=\\\"billing\\\">Billing</option>\u001b[39m\n\u001b[31m          <option value=\\\"shipping\\\">Shipping</option>\u001b[39m\n\u001b[31m          <option value=\\\"site\\\">Site</option>\u001b[39m\n\u001b[31m          <option value=\\\"warehouse\\\">Warehouse</option>\u001b[39m\n\u001b[31m          <option value=\\\"other\\\">Other</option>\u001b[39m\n\u001b[31m        </select>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>Pincode</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-pincode\\\" maxlength=\\\"6\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 1 <span class=\\\"req\\\">*</span></label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line1\\\" maxlength=\\\"240\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label>Line 2</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-line2\\\" maxlength=\\\"200\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>City</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-city\\\" maxlength=\\\"80\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field\\\">\u001b[39m\n\u001b[31m        <label>State</label>\u001b[39m\n\u001b[31m        <input id=\\\"af-state\\\" maxlength=\\\"60\\\">\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"field full\\\">\u001b[39m\n\u001b[31m        <label style=\\\"display:flex;align-items:center;gap:6px;cursor:pointer\\\">\u001b[39m\n\u001b[31m          <input type=\\\"checkbox\\\" id=\\\"af-is_default\\\">\u001b[39m\n\u001b[31m          <span>Mark as default</span>\u001b[39m\n\u001b[31m        </label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:14px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-address')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" onclick=\\\"saveAddress()\\\">Save</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<div id=\\\"toast\\\"></div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m'use strict';·\u001b[39m\n\u001b[31mconst CACHE_VER = 'counters-v40';\u001b[39m\n\u001b[31mconst DB        = '/db';·\u001b[39m\n\u001b[31m// ── Session + auth ────────────────────────────────────────\u001b[39m\n\u001b[31mlet session = null, myId = '', myName = '';\u001b[39m\n\u001b[31mconst SUPERVISORS = ['rakesh'];                     // role-derived\u001b[39m\n\u001b[31mconst ALWAYS_SUPERVISOR = ['harish', 'pramod'];     // hardcoded fallback per user note\u001b[39m\n\u001b[31mfunction isSupervisor() { return SUPERVISORS.includes(myId) || ALWAYS_SUPERVISOR.includes(myId); }·\u001b[39m\n\u001b[31m// ADR-112: always-visible Logout (was missing entirely — only Hub navigation existed)\u001b[39m\n\u001b[31mfunction logout() { localStorage.removeItem('lm360-session'); window.location.href = '/hub/'; }·\u001b[39m\n\u001b[31mfunction bootSession() {\u001b[39m\n\u001b[31m  try { session = JSON.parse(localStorage.getItem('lm360-session') || 'null'); } catch (_) {}\u001b[39m\n\u001b[31m  if (!session || !session.empId) {\u001b[39m\n\u001b[31m    const next = encodeURIComponent(location.pathname + location.search);\u001b[39m\n\u001b[31m    location.replace(`/hub/?next=${next}`);\u001b[39m\n\u001b[31m    return false;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  myId   = session.empId;\u001b[39m\n\u001b[31m  myName = session.name || session.empId;\u001b[39m\n\u001b[31m  document.getElementById('user-pill').innerHTML =\u001b[39m\n\u001b[31m    `<b>${esc(myName)}</b>${isSupervisor() ? '<span class=\\\"role-tag\\\">SUPERVISOR</span>' : ''}`;\u001b[39m\n\u001b[31m  // All users can access uploads (to submit CSVs); supervisors get extra tools\u001b[39m\n\u001b[31m  document.getElementById('btn-uploads').style.display = '';\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    document.getElementById('btn-new').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-bulk').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-geofill').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-multirecce').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-clients').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-notify').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-audit').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-coverage').style.display = '';\u001b[39m\n\u001b[31m    document.getElementById('btn-ai-import').style.display = '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return true;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── DB helpers ────────────────────────────────────────────\u001b[39m\n\u001b[31masync function api(path, opts = {}) {\u001b[39m\n\u001b[31m  const headers = {\u001b[39m\n\u001b[31m    'Accept-Profile':  'counters',\u001b[39m\n\u001b[31m    'Content-Profile': 'counters',\u001b[39m\n\u001b[31m    'Accept':          'application/json',\u001b[39m\n\u001b[31m    ...(opts.body ? { 'Content-Type': 'application/json' } : {}),\u001b[39m\n\u001b[31m    ...(opts.headers || {}),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const r = await fetch(DB + path, { ...opts, headers });\u001b[39m\n\u001b[31m  if (!r.ok) {\u001b[39m\n\u001b[31m    const text = await r.text().catch(() => '');\u001b[39m\n\u001b[31m    throw new Error(`${r.status} ${text.slice(0, 200)}`);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (r.status === 204) return null;\u001b[39m\n\u001b[31m  const ct = r.headers.get('content-type') || '';\u001b[39m\n\u001b[31m  return ct.includes('json') ? r.json() : r.text();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ID/Name policy (ADR-130 §5): counters.allocation/upload and recce.submissions\u001b[39m\n\u001b[31m// all carry a genuine job_id FK to sales.jobs — resolve it to job_name via a\u001b[39m\n\u001b[31m// single batched cross-schema lookup (never per-row), cached for the session.\u001b[39m\n\u001b[31m// Returns whether anything was actually newly cached (false on a failed/offline\u001b[39m\n\u001b[31m// fetch), so callers that re-render on \\\"true\\\" don't retry-storm when offline.\u001b[39m\n\u001b[31mconst _jobNameCache = {};\u001b[39m\n\u001b[31masync function _ensureJobNames(jobIds) {\u001b[39m\n\u001b[31m  const missing = [...new Set(jobIds)].filter(Boolean).filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (!missing.length) return false;\u001b[39m\n\u001b[31m  let added = false;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/jobs?job_id=in.(${missing.map(encodeURIComponent).join(',')})&select=job_id,job_name`,\u001b[39m\n\u001b[31m      { headers: { 'Accept-Profile': 'sales', 'Accept': 'application/json' } });\u001b[39m\n\u001b[31m    const rows = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    rows.forEach(j => { _jobNameCache[j.job_id] = j.job_name; added = true; });\u001b[39m\n\u001b[31m  } catch (_) { /* leave uncached — falls back to raw id this render */ }\u001b[39m\n\u001b[31m  return added;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── State ─────────────────────────────────────────────────\u001b[39m\n\u001b[31mlet allCounters = [];     // counter_v rows\u001b[39m\n\u001b[31mlet activeFilter = 'active';\u001b[39m\n\u001b[31mconst _listChips = { state: new Set(), tier: new Set(), store_type: new Set(), geo_cluster: new Set(), channel: new Set(), type: new Set() };\u001b[39m\n\u001b[31mlet detailCounter = null; // currently-open counter row\u001b[39m\n\u001b[31mlet detailContacts = [];\u001b[39m\n\u001b[31mlet detailAddresses = [];\u001b[39m\n\u001b[31mlet detailRecces = [];\u001b[39m\n\u001b[31mlet _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31mlet detailBrands = [];        // counter_brand rows (available after v19 migration)\u001b[39m\n\u001b[31mlet detailAllocations = [];   // allocation rows for current counter\u001b[39m\n\u001b[31mlet editingId = null;     // null = creating; counter_id = editing\u001b[39m\n\u001b[31mlet editingContact = null;\u001b[39m\n\u001b[31mlet editingAddress = null;\u001b[39m\n\u001b[31mlet brandSet = new Set();\u001b[39m\n\u001b[31mlet _toastTimer;\u001b[39m\n\u001b[31mlet _dedupTimer;\u001b[39m\n\u001b[31mlet _dedupMatches = [];·\u001b[39m\n\u001b[31m// ── Utilities ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction esc(s) { return (s == null ? '' : String(s)).replace(/[&<>\\\"']/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\\\"':'&quot;',\\\"'\\\":'&#39;'}[c])); }\u001b[39m\n\u001b[31mfunction toast(msg, type = '') {\u001b[39m\n\u001b[31m  const el = document.getElementById('toast');\u001b[39m\n\u001b[31m  el.textContent = msg;\u001b[39m\n\u001b[31m  el.className = 'show ' + type;\u001b[39m\n\u001b[31m  clearTimeout(_toastTimer);\u001b[39m\n\u001b[31m  _toastTimer = setTimeout(() => el.className = type, 2500);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction showScreen(id) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.scr').forEach(s => s.classList.remove('active'));\u001b[39m\n\u001b[31m  document.getElementById(id).classList.add('active');\u001b[39m\n\u001b[31m  window.scrollTo(0, 0);\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction upper(el) { el.value = (el.value || '').toUpperCase(); }\u001b[39m\n\u001b[31mfunction reload() { location.reload(); }\u001b[39m\n\u001b[31mfunction slugify(s) { return (s || '').toLowerCase().replace(/[^a-z0-9]+/g, '_').replace(/^_+|_+$/g, '').slice(0, 40); }\u001b[39m\n\u001b[31mfunction newId(displayName) {\u001b[39m\n\u001b[31m  const slug = slugify(displayName) || 'counter';\u001b[39m\n\u001b[31m  const suffix = Math.floor(Math.random() * 0xffffff).toString(36).padStart(4, '0');\u001b[39m\n\u001b[31m  return `cntr-${slug}-${suffix}`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction fmtDate(d) { if (!d) return ''; try { return new Date(d).toLocaleDateString('en-IN'); } catch { return d; } }\u001b[39m\n\u001b[31mfunction daysAgo(d) {\u001b[39m\n\u001b[31m  if (!d) return '';\u001b[39m\n\u001b[31m  const diff = Math.floor((Date.now() - new Date(d).getTime()) / 86400000);\u001b[39m\n\u001b[31m  if (diff === 0) return 'today';\u001b[39m\n\u001b[31m  if (diff === 1) return '1 day ago';\u001b[39m\n\u001b[31m  if (diff < 30) return `${diff} days ago`;\u001b[39m\n\u001b[31m  if (diff < 60) return '~1 month ago';\u001b[39m\n\u001b[31m  const months = Math.floor(diff / 30);\u001b[39m\n\u001b[31m  return `${months} months ago`;\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction levenshtein(a, b) {\u001b[39m\n\u001b[31m  if (!a || !b) return Math.max((a||'').length, (b||'').length);\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length: m+1}, () => new Array(n+1).fill(0));\u001b[39m\n\u001b[31m  for (let i = 0; i <= m; i++) dp[i][0] = i;\u001b[39m\n\u001b[31m  for (let j = 0; j <= n; j++) dp[0][j] = j;\u001b[39m\n\u001b[31m  for (let i = 1; i <= m; i++) for (let j = 1; j <= n; j++) {\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1] === b[j-1] ? dp[i-1][j-1] : 1 + Math.min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── List screen ───────────────────────────────────────────\u001b[39m\n\u001b[31masync function loadList() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    allCounters = await api('/counter_v?order=display_name.asc&limit=2000');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    allCounters = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (isSupervisor()) {\u001b[39m\n\u001b[31m    checkInactiveAllocBanner();\u001b[39m\n\u001b[31m    loadEmployees().then(() => _populateListFilters()); // pre-load so rep filter dropdown is populated\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Checks if any inactive employees have counters allocated; shows warning banner if so\u001b[39m\n\u001b[31mlet _inactiveAllocEmps = []; // [{id, name, count}] — populated by the banner check\u001b[39m\n\u001b[31masync function checkInactiveAllocBanner() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [allocRows, empRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id&limit=2000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/employees?active=is.false&select=id,name`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    if (!empRows.length) return;\u001b[39m\n\u001b[31m    const inactiveIds = new Set(empRows.map(e => e.id));\u001b[39m\n\u001b[31m    const countsByEmp = {};\u001b[39m\n\u001b[31m    allocRows.forEach(a => {\u001b[39m\n\u001b[31m      if (inactiveIds.has(a.employee_id)) {\u001b[39m\n\u001b[31m        countsByEmp[a.employee_id] = (countsByEmp[a.employee_id] || 0) + 1;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const affected = Object.entries(countsByEmp).map(([id, count]) => {\u001b[39m\n\u001b[31m      const emp = empRows.find(e => e.id === id);\u001b[39m\n\u001b[31m      return { id, name: emp?.name || id, count };\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    _inactiveAllocEmps = affected;\u001b[39m\n\u001b[31m    const banner = document.getElementById('alloc-warn-banner');\u001b[39m\n\u001b[31m    if (!banner) return;\u001b[39m\n\u001b[31m    if (!affected.length) { banner.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const txt = affected.map(e => `${esc(e.name)} (${e.count})`).join(', ');\u001b[39m\n\u001b[31m    document.getElementById('alloc-warn-text').innerHTML =\u001b[39m\n\u001b[31m      `⚠️ Inactive: ${txt} — these counters have no active field rep`;\u001b[39m\n\u001b[31m    banner.classList.add('show');\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31masync function filterByInactiveAlloc() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  // Fetch counter_ids for inactive employees\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const inactiveIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const rows = await api(`/allocation?employee_id=in.(${inactiveIds.map(encodeURIComponent).join(',')})&select=counter_id,employee_id&limit=2000`);\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set(rows.map(r => r.counter_id));\u001b[39m\n\u001b[31m    // Store full rows for bulk reassign\u001b[39m\n\u001b[31m    _inactiveAllocRows = rows;\u001b[39m\n\u001b[31m  } catch { return; }\u001b[39m\n\u001b[31m  // Switch to \\\"all\\\" tile and set a custom filter mode\u001b[39m\n\u001b[31m  activeFilter = 'inactive_alloc';\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.remove('sel'));\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  document.getElementById('alloc-warn-banner').classList.remove('show');\u001b[39m\n\u001b[31m  toast(`Showing ${_inactiveAllocCounterIds.size} counters from inactive employees`, 'info');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _inactiveAllocRows = []; // full allocation rows for inactive employees·\u001b[39m\n\u001b[31mfunction onRepFilterChange() {\u001b[39m\n\u001b[31m  const repId = document.getElementById('list-filter-rep')?.value || '';\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m  if (repId) {\u001b[39m\n\u001b[31m    const emp = _allEmps.find(e => e.id === repId);\u001b[39m\n\u001b[31m    const cnt = allCounters.filter(c => (c.allocated_emp_ids || []).includes(repId)).length;\u001b[39m\n\u001b[31m    if (emp) toast(`Showing ${cnt} counters for ${emp.name}`, 'info');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openBulkReassign() {\u001b[39m\n\u001b[31m  if (!_inactiveAllocEmps.length) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  const modal = document.getElementById('modal-bulk-reassign');\u001b[39m\n\u001b[31m  const fromEl = document.getElementById('bulk-reassign-from');\u001b[39m\n\u001b[31m  const toEl = document.getElementById('bulk-reassign-to');\u001b[39m\n\u001b[31m  const summEl = document.getElementById('bulk-reassign-summary');\u001b[39m\n\u001b[31m  // Build from text\u001b[39m\n\u001b[31m  fromEl.innerHTML = _inactiveAllocEmps.map(e =>\u001b[39m\n\u001b[31m    `<span style=\\\"background:rgba(249,115,22,.15);border:1px solid rgba(249,115,22,.3);border-radius:6px;padding:3px 8px;font-size:12px;font-weight:700\\\">${esc(e.name)} · ${e.count} counter${e.count !== 1 ? 's' : ''}</span>`\u001b[39m\n\u001b[31m  ).join(' ');\u001b[39m\n\u001b[31m  // Populate active employees in the dropdown\u001b[39m\n\u001b[31m  toEl.innerHTML = '<option value=\\\"\\\">— Select active employee —</option>' +\u001b[39m\n\u001b[31m    _allEmps.map(e => `<option value=\\\"${esc(e.id)}\\\">${esc(e.name)}${e.role ? ' · ' + esc(e.role) : ''}</option>`).join('');\u001b[39m\n\u001b[31m  summEl.textContent = `${_inactiveAllocCounterIds.size} counter${_inactiveAllocCounterIds.size !== 1 ? 's' : ''} will be reassigned.`;\u001b[39m\n\u001b[31m  modal.classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function confirmBulkReassign() {\u001b[39m\n\u001b[31m  const toEmpId = document.getElementById('bulk-reassign-to').value;\u001b[39m\n\u001b[31m  if (!toEmpId) { toast('Select an employee to reassign to', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-do-bulk-reassign');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Reassigning…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Group by from-employee so we can delete their allocations\u001b[39m\n\u001b[31m    const fromIds = _inactiveAllocEmps.map(e => e.id);\u001b[39m\n\u001b[31m    const counterIds = [..._inactiveAllocCounterIds];\u001b[39m\n\u001b[31m    // Delete allocations for each inactive employee\u001b[39m\n\u001b[31m    for (const fromId of fromIds) {\u001b[39m\n\u001b[31m      await api(`/allocation?employee_id=eq.${encodeURIComponent(fromId)}&counter_id=in.(${counterIds.map(encodeURIComponent).join(',')})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create new allocations for the target employee\u001b[39m\n\u001b[31m    const newRows = counterIds.map(cid => ({ employee_id: toEmpId, counter_id: cid }));\u001b[39m\n\u001b[31m    await api('/allocation', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify(newRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-bulk-reassign');\u001b[39m\n\u001b[31m    // Reset filter and reload\u001b[39m\n\u001b[31m    activeFilter = 'all';\u001b[39m\n\u001b[31m    _inactiveAllocEmps = [];\u001b[39m\n\u001b[31m    _inactiveAllocCounterIds = new Set();\u001b[39m\n\u001b[31m    _inactiveAllocRows = [];\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m    const toEmp = _allEmps.find(e => e.id === toEmpId);\u001b[39m\n\u001b[31m    toast(`✓ ${counterIds.length} counters reassigned to ${toEmp?.name || toEmpId}`, 'success');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reassign failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = '⇄ Reassign All';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Chip filter engine (ADR-027 / ADR-061) ───────────────────────────────·\u001b[39m\n\u001b[31mfunction computeCounterAvail(subset) {\u001b[39m\n\u001b[31m  const c = { state:{}, tier:{}, store_type:{}, geo_cluster:{}, channel:{}, type:{} };\u001b[39m\n\u001b[31m  for (const r of subset) {\u001b[39m\n\u001b[31m    if (r.state_code)   c.state[r.state_code]          = (c.state[r.state_code]||0)+1;\u001b[39m\n\u001b[31m    if (r.city_tier)    c.tier[r.city_tier]             = (c.tier[r.city_tier]||0)+1;\u001b[39m\n\u001b[31m    if (r.store_type)   c.store_type[r.store_type]      = (c.store_type[r.store_type]||0)+1;\u001b[39m\n\u001b[31m    if (r.geo_cluster)  c.geo_cluster[r.geo_cluster]    = (c.geo_cluster[r.geo_cluster]||0)+1;\u001b[39m\n\u001b[31m    if (r.channel)      c.channel[r.channel]            = (c.channel[r.channel]||0)+1;\u001b[39m\n\u001b[31m    if (r.counter_type) c.type[r.counter_type]          = (c.type[r.counter_type]||0)+1;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  return c;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listChipToggle(dim, val) {\u001b[39m\n\u001b[31m  const s = _listChips[dim];\u001b[39m\n\u001b[31m  if (s.has(val)) s.delete(val); else s.add(val);\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _renderListChips(list) {\u001b[39m\n\u001b[31m  const avail    = computeCounterAvail(list);\u001b[39m\n\u001b[31m  const anyActive = Object.values(_listChips).some(s => s.size);·\u001b[39m\n\u001b[31m  const FIXED = {\u001b[39m\n\u001b[31m    state:      [{val:'PB',label:'Punjab'},{val:'HP',label:'Himachal'},{val:'JK',label:'J&K'},\u001b[39m\n\u001b[31m                 {val:'HR',label:'Haryana'},{val:'CH',label:'Chd'},{val:'DL',label:'Delhi'}],\u001b[39m\n\u001b[31m    tier:       [{val:'tier1',label:'Tier 1'},{val:'tier2',label:'Tier 2'},\u001b[39m\n\u001b[31m                 {val:'tier3',label:'Tier 3'},{val:'tier4',label:'Tier 4'}],\u001b[39m\n\u001b[31m    store_type: [{val:'les',label:'LES'},{val:'ses',label:'SES'},{val:'mbo',label:'MBO'},\u001b[39m\n\u001b[31m                 {val:'ebo',label:'EBO'},{val:'modern_trade',label:'Modern Trade'},{val:'other',label:'Other'}],\u001b[39m\n\u001b[31m    geo_cluster:[{val:'tricity',label:'Tricity'},{val:'ncr',label:'NCR'},{val:'majha',label:'Majha'},\u001b[39m\n\u001b[31m                 {val:'doaba',label:'Doaba'},{val:'malwa',label:'Malwa'},{val:'kashmir_valley',label:'Kashmir Valley'}],\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const LABELS = { state:'State', tier:'City Tier', store_type:'Store Type', geo_cluster:'Geo Cluster',\u001b[39m\n\u001b[31m                   channel:'Channel', type:'Counter Type' };·\u001b[39m\n\u001b[31m  function chipRow(dim, chips, activeSet, dimAvail) {\u001b[39m\n\u001b[31m    let html = '';\u001b[39m\n\u001b[31m    for (const {val, label} of chips) {\u001b[39m\n\u001b[31m      const n       = dimAvail[val] || 0;\u001b[39m\n\u001b[31m      const active  = activeSet.has(val);\u001b[39m\n\u001b[31m      const unavail = n === 0 && !active && anyActive;\u001b[39m\n\u001b[31m      const badge   = n > 0 ? ` <span style=\\\"font-size:9px;opacity:.6\\\">(${n})</span>` : '';\u001b[39m\n\u001b[31m      html += `<button class=\\\"filter-chip${active?' active':''}${unavail?' unavailable':''}\\\" onclick=\\\"listChipToggle('${dim}','${esc(val)}')\\\">${esc(label)}${badge}</button>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (activeSet.size) html += `<button class=\\\"filter-chip\\\" style=\\\"color:var(--err);border-color:var(--err)\\\" onclick=\\\"listClearDim('${dim}')\\\">✕</button>`;\u001b[39m\n\u001b[31m    return html;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let html = '<div class=\\\"filter-rows-wrap\\\">';\u001b[39m\n\u001b[31m  // Fixed dimensions\u001b[39m\n\u001b[31m  for (const [dim, chips] of Object.entries(FIXED)) {\u001b[39m\n\u001b[31m    // Only show geo_cluster row if any counter has a geo_cluster set\u001b[39m\n\u001b[31m    if (dim === 'geo_cluster' && !allCounters.some(c => c.geo_cluster)) continue;\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS[dim]}</div><div class=\\\"filter-row\\\">${chipRow(dim, chips, _listChips[dim], avail[dim])}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Channel (show if ≥2 unique values in full dataset)\u001b[39m\n\u001b[31m  const channels = [...new Set(allCounters.map(c => c.channel).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (channels.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.channel}</div><div class=\\\"filter-row\\\">${chipRow('channel', channels.map(v=>({val:v,label:v})), _listChips.channel, avail.channel)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Dynamic: Counter Type (show if ≥2 unique values)\u001b[39m\n\u001b[31m  const types = [...new Set(allCounters.map(c => c.counter_type).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  if (types.length >= 2) {\u001b[39m\n\u001b[31m    html += `<div class=\\\"filter-section\\\"><div class=\\\"filter-label\\\">${LABELS.type}</div><div class=\\\"filter-row\\\">${chipRow('type', types.map(v=>({val:v,label:v})), _listChips.type, avail.type)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  html += '</div>';\u001b[39m\n\u001b[31m  const el = document.getElementById('list-chip-rows');\u001b[39m\n\u001b[31m  if (el) el.innerHTML = html;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction listClearDim(dim) {\u001b[39m\n\u001b[31m  _listChips[dim].clear();\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTile(ev, key) {\u001b[39m\n\u001b[31m  activeFilter = key;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === key));\u001b[39m\n\u001b[31m  // Auto-set sort to \\\"Stale first\\\" when switching to No Recce or Stale 90d tile\u001b[39m\n\u001b[31m  if (key === 'norecce' || key === 'stale90') {\u001b[39m\n\u001b[31m    const sortEl = document.getElementById('list-sort');\u001b[39m\n\u001b[31m    if (sortEl && sortEl.value === 'name') sortEl.value = 'stale';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// All non-status filters (text, brand, territory, rep, chips). Used by filterCounters()\u001b[39m\n\u001b[31m// and renderList() for tile counts/availability.\u001b[39m\n\u001b[31mfunction _applyNonStatusFilters(source) {\u001b[39m\n\u001b[31m  const q          = (document.getElementById('srch').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m  const fBrand     = (document.getElementById('list-filter-brand')     ||{}).value || '';\u001b[39m\n\u001b[31m  const fTerritory = (document.getElementById('list-filter-territory') ||{}).value || '';\u001b[39m\n\u001b[31m  const fRep       = (document.getElementById('list-filter-rep')       ||{}).value || '';\u001b[39m\n\u001b[31m  return source.filter(c => {\u001b[39m\n\u001b[31m    if (fBrand     && !(c.brand_codes||[]).includes(fBrand))                        return false;\u001b[39m\n\u001b[31m    if (fTerritory && (c.territory||'').toLowerCase() !== fTerritory.toLowerCase()) return false;\u001b[39m\n\u001b[31m    if (fRep       && !(c.allocated_emp_ids||[]).includes(fRep))                    return false;\u001b[39m\n\u001b[31m    if (_listChips.state.size      && !_listChips.state.has(c.state_code))          return false;\u001b[39m\n\u001b[31m    if (_listChips.tier.size       && !_listChips.tier.has(c.city_tier))            return false;\u001b[39m\n\u001b[31m    if (_listChips.store_type.size && !_listChips.store_type.has(c.store_type))     return false;\u001b[39m\n\u001b[31m    if (_listChips.geo_cluster.size && !_listChips.geo_cluster.has(c.geo_cluster))  return false;\u001b[39m\n\u001b[31m    if (_listChips.channel.size    && !_listChips.channel.has(c.channel))           return false;\u001b[39m\n\u001b[31m    if (_listChips.type.size       && !_listChips.type.has(c.counter_type))         return false;\u001b[39m\n\u001b[31m    if (!q) return true;\u001b[39m\n\u001b[31m    const hay = [\u001b[39m\n\u001b[31m      c.display_name, c.legal_name, c.proprietor_name,\u001b[39m\n\u001b[31m      ...(c.aliases||[]), ...(c.brand_codes||[]),\u001b[39m\n\u001b[31m      c.gstin, c.pan, c.address_line1, c.city, c.state, c.pincode, c.territory, c.distributor_name,\u001b[39m\n\u001b[31m      c.channel, c.customer_group, c.counter_type,\u001b[39m\n\u001b[31m      c.primary_contact_phone, c.primary_contact_name,\u001b[39m\n\u001b[31m    ].filter(Boolean).join(' ').toLowerCase();\u001b[39m\n\u001b[31m    return hay.includes(q);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction filterCounters() {\u001b[39m\n\u001b[31m  const _s90cut = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m  let list = _applyNonStatusFilters(allCounters).filter(c => {\u001b[39m\n\u001b[31m    if (activeFilter === 'all')            { /* keep */ }\u001b[39m\n\u001b[31m    else if (activeFilter === 'pending')        { if (c.approval_status !== 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive')       { if (c.is_active) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'active')         { if (!c.is_active || c.approval_status === 'pending') return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'norecce')        { if (!c.is_active || c.approval_status === 'pending' || c.recce_count > 0) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'stale90')        { if (!c.is_active || c.approval_status === 'pending' || !(c.recce_count > 0) || !c.last_recce_date || c.last_recce_date >= _s90cut) return false; }\u001b[39m\n\u001b[31m    else if (activeFilter === 'inactive_alloc') { if (!_inactiveAllocCounterIds.has(c.counter_id)) return false; }\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const sort = (document.getElementById('list-sort')||{}).value || 'name';\u001b[39m\n\u001b[31m  if (sort === 'recce_desc') list.sort((a, b) => (b.recce_count||0) - (a.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recce_asc')  list.sort((a, b) => (a.recce_count||0) - (b.recce_count||0));\u001b[39m\n\u001b[31m  else if (sort === 'recent') list.sort((a, b) => {\u001b[39m\n\u001b[31m    if (!a.last_recce_date && !b.last_recce_date) return 0;\u001b[39m\n\u001b[31m    if (!a.last_recce_date) return 1; if (!b.last_recce_date) return -1;\u001b[39m\n\u001b[31m    return b.last_recce_date.localeCompare(a.last_recce_date);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  else if (sort === 'stale') list.sort((a, b) => {\u001b[39m\n\u001b[31m    const ra = a.recce_count||0, rb = b.recce_count||0;\u001b[39m\n\u001b[31m    if (ra === 0 && rb > 0) return -1; if (ra > 0 && rb === 0) return 1;\u001b[39m\n\u001b[31m    if (ra === 0 && rb === 0) return (a.display_name||'').localeCompare(b.display_name||'');\u001b[39m\n\u001b[31m    const da = a.last_recce_date||'9999', db = b.last_recce_date||'9999';\u001b[39m\n\u001b[31m    return da < db ? -1 : da > db ? 1 : 0;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  return list;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _populateListFilters() {\u001b[39m\n\u001b[31m  // Brand and Territory: too many/dynamic options → keep as <select>\u001b[39m\n\u001b[31m  const brands      = [...new Set(allCounters.flatMap(c => c.brand_codes||[]))].sort();\u001b[39m\n\u001b[31m  const territories = [...new Set(allCounters.map(c => c.territory).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const sb = document.getElementById('list-filter-brand');\u001b[39m\n\u001b[31m  const st = document.getElementById('list-filter-territory');\u001b[39m\n\u001b[31m  if (sb) {\u001b[39m\n\u001b[31m    const prev = sb.value;\u001b[39m\n\u001b[31m    sb.innerHTML = '<option value=\\\"\\\">All Brands</option>' +\u001b[39m\n\u001b[31m      brands.map(b => `<option value=\\\"${esc(b)}\\\"${b===prev?' selected':''}>${esc(b)}</option>`).join('');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (st) {\u001b[39m\n\u001b[31m    const prev = st.value;\u001b[39m\n\u001b[31m    st.innerHTML = '<option value=\\\"\\\">All Territories</option>' +\u001b[39m\n\u001b[31m      territories.map(t => `<option value=\\\"${esc(t)}\\\"${t===prev?' selected':''}>${esc(t)}</option>`).join('');\u001b[39m\n\u001b[31m    st.style.display = territories.length ? '' : 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Rep: supervisor-only. Build from allCounters allocations (includes inactive)\u001b[39m\n\u001b[31m  // merged with _allEmps (active only) so every name that ever had an allocation shows.\u001b[39m\n\u001b[31m  const sr = document.getElementById('list-filter-rep');\u001b[39m\n\u001b[31m  if (sr && isSupervisor()) {\u001b[39m\n\u001b[31m    const prev = sr.value;\u001b[39m\n\u001b[31m    // Collect all (id → name) from allCounters allocation arrays\u001b[39m\n\u001b[31m    const empMap = new Map(); // id → { name, active }\u001b[39m\n\u001b[31m    allCounters.forEach(c => {\u001b[39m\n\u001b[31m      const ids   = c.allocated_emp_ids || [];\u001b[39m\n\u001b[31m      const names = c.allocated_to      || [];\u001b[39m\n\u001b[31m      ids.forEach((id, i) => { if (id && !empMap.has(id)) empMap.set(id, { name: names[i] || id, active: false }); });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark active employees (from _allEmps)\u001b[39m\n\u001b[31m    _allEmps.forEach(e => {\u001b[39m\n\u001b[31m      if (empMap.has(e.id)) empMap.get(e.id).active = true;\u001b[39m\n\u001b[31m      else empMap.set(e.id, { name: e.name, active: true });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Sort: active first (alphabetical), then inactive (alphabetical)\u001b[39m\n\u001b[31m    const sorted = [...empMap.entries()]\u001b[39m\n\u001b[31m      .map(([id, d]) => ({ id, name: d.name, active: d.active }))\u001b[39m\n\u001b[31m      .sort((a, b) => {\u001b[39m\n\u001b[31m        if (a.active !== b.active) return a.active ? -1 : 1;\u001b[39m\n\u001b[31m        return a.name.localeCompare(b.name);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    sr.innerHTML = '<option value=\\\"\\\">All Reps</option>' +\u001b[39m\n\u001b[31m      sorted.map(e => `<option value=\\\"${esc(e.id)}\\\"${e.id===prev?' selected':''}>${esc(e.name)}${e.active?'':' (inactive)'}</option>`).join('');\u001b[39m\n\u001b[31m    sr.style.display = '';\u001b[39m\n\u001b[31m  } else if (sr) {\u001b[39m\n\u001b[31m    sr.style.display = 'none';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderList() {\u001b[39m\n\u001b[31m  const stale90cutoff = new Date(Date.now() - 90 * 86400000).toISOString().slice(0, 10);·\u001b[39m\n\u001b[31m  // Tile counts from pre-status subset (cross-filtered by text/brand/territory/rep/chips)\u001b[39m\n\u001b[31m  const preStatus = _applyNonStatusFilters(allCounters);\u001b[39m\n\u001b[31m  const counts = {\u001b[39m\n\u001b[31m    all:      preStatus.length,\u001b[39m\n\u001b[31m    active:   preStatus.filter(c => c.is_active && c.approval_status !== 'pending').length,\u001b[39m\n\u001b[31m    pending:  preStatus.filter(c => c.approval_status === 'pending').length,\u001b[39m\n\u001b[31m    norecce:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && !(c.recce_count > 0)).length,\u001b[39m\n\u001b[31m    stale90:  preStatus.filter(c => c.is_active && c.approval_status !== 'pending' && c.recce_count > 0 && c.last_recce_date && c.last_recce_date < stale90cutoff).length,\u001b[39m\n\u001b[31m    inactive: preStatus.filter(c => !c.is_active).length,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  document.getElementById('t-all').textContent      = counts.all;\u001b[39m\n\u001b[31m  document.getElementById('t-active').textContent   = counts.active;\u001b[39m\n\u001b[31m  document.getElementById('t-pending').textContent  = counts.pending;\u001b[39m\n\u001b[31m  document.getElementById('t-norecce').textContent  = counts.norecce;\u001b[39m\n\u001b[31m  const stale90El = document.getElementById('t-stale90');\u001b[39m\n\u001b[31m  if (stale90El) stale90El.textContent = counts.stale90;\u001b[39m\n\u001b[31m  document.getElementById('t-inactive').textContent = counts.inactive;\u001b[39m\n\u001b[31m  document.querySelectorAll('.tile').forEach(t => t.classList.toggle('sel', t.dataset.filter === activeFilter));·\u001b[39m\n\u001b[31m  // Summary tiles (from pre-status — before status filter but after all other filters)\u001b[39m\n\u001b[31m  const gpsCount   = preStatus.filter(c => c.gps_lat && c.gps_lng).length;\u001b[39m\n\u001b[31m  const recceCount = preStatus.filter(c => c.recce_count > 0).length;\u001b[39m\n\u001b[31m  const anyChip    = Object.values(_listChips).some(s => s.size);\u001b[39m\n\u001b[31m  const tilesEl    = document.getElementById('list-sum-tiles');\u001b[39m\n\u001b[31m  if (tilesEl) {\u001b[39m\n\u001b[31m    tilesEl.innerHTML = [\u001b[39m\n\u001b[31m      { icon:'🏪', val: anyChip ? `${preStatus.length}/${allCounters.length}` : preStatus.length, label:'Counters' },\u001b[39m\n\u001b[31m      { icon:'✅', val: counts.active,  label:'Active' },\u001b[39m\n\u001b[31m      { icon:'📍', val: gpsCount,       label:'GPS Ready' },\u001b[39m\n\u001b[31m      { icon:'📸', val: recceCount,     label:'Recce\\\\'d' },\u001b[39m\n\u001b[31m    ].map(t => `<div class=\\\"sum-tile\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px\\\">${t.icon}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:14px;font-weight:800;color:var(--accent)\\\">${t.val}</div>\u001b[39m\n\u001b[31m      <div style=\\\"font-size:8.5px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px\\\">${t.label}</div>\u001b[39m\n\u001b[31m    </div>`).join('');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  _populateListFilters();·\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  _renderListChips(list);\u001b[39m\n\u001b[31m  document.getElementById('list-count').textContent = `${list.length} of ${allCounters.length}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('list-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏪</div><div>No counters yet${isSupervisor() ? '. Tap + New to add one.' : '.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const approveAllBanner = (activeFilter === 'pending' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <span>${list.length} counter${list.length !== 1 ? 's' : ''} awaiting approval</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveAllPending()\\\" style=\\\"font-size:11px;padding:5px 10px\\\">✓ Approve All</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const inactiveAllocBanner = (activeFilter === 'inactive_alloc' && isSupervisor() && _inactiveAllocEmps.length)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(249,115,22,.08);border:1px solid rgba(249,115,22,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>👥 ${list.length} counter${list.length !== 1 ? 's' : ''} from inactive field reps — reassign to an active rep</span>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"openBulkReassign()\\\" style=\\\"font-size:11px;padding:5px 10px;background:#ea580c;flex-shrink:0\\\">⇄ Reassign</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const noRecceBanner = (activeFilter === 'norecce' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(59,130,246,.08);border:1px solid rgba(59,130,246,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>📸 ${list.length} counter${list.length !== 1 ? 's' : ''} never visited — see who's responsible in Coverage Report</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport()\\\" style=\\\"background:rgba(59,130,246,.2);border:1px solid rgba(59,130,246,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  const stale90Banner = (activeFilter === 'stale90' && isSupervisor() && list.length > 0)\u001b[39m\n\u001b[31m    ? `<div style=\\\"display:flex;align-items:center;justify-content:space-between;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.3);border-radius:8px;padding:8px 12px;margin-bottom:8px;font-size:12.5px;gap:8px\\\">\u001b[39m\n\u001b[31m        <span>⏰ ${list.length} counter${list.length !== 1 ? 's' : ''} not visited in 90+ days — needs a re-Recce</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"openCoverageReport(90)\\\" style=\\\"background:rgba(245,158,11,.2);border:1px solid rgba(245,158,11,.4);color:var(--text);font-size:11px;font-weight:700;padding:4px 10px;border-radius:10px;cursor:pointer;white-space:nowrap;flex-shrink:0\\\">📊 Coverage</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  out.innerHTML = approveAllBanner + inactiveAllocBanner + noRecceBanner + stale90Banner + list.map(c => {\u001b[39m\n\u001b[31m    const brands = (c.brand_codes || []).slice(0, 4).map(b => `<span class=\\\"brand-chip\\\">${esc(b)}</span>`).join('');\u001b[39m\n\u001b[31m    const more   = (c.brand_codes || []).length > 4 ? `<span class=\\\"brand-chip\\\">+${(c.brand_codes||[]).length - 4}</span>` : '';\u001b[39m\n\u001b[31m    const statusClass = !c.is_active ? 'inactive' : c.approval_status === 'pending' ? 'pending_review' : c.status;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"c-row\\\" onclick=\\\"openDetail('${esc(c.counter_id)}')\\\">\u001b[39m\n\u001b[31m        <div class=\\\"c-row-top\\\">\u001b[39m\n\u001b[31m          <div class=\\\"c-row-name\\\">${esc(c.display_name)}</div>\u001b[39m\n\u001b[31m          <span class=\\\"status-chip ${statusClass}\\\">${statusClass === 'pending_review' ? 'pending' : statusClass}</span>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<button onclick=\\\"event.stopPropagation();quickAllocate('${esc(c.counter_id)}')\\\" title=\\\"Quick allocate\\\" style=\\\"background:none;border:none;font-size:15px;cursor:pointer;padding:2px 4px;color:var(--muted);flex-shrink:0\\\">👥</button>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"c-row-meta\\\">\u001b[39m\n\u001b[31m          ${c.city ? `<span>📍 ${esc(c.city)}${c.pincode ? ' · ' + esc(c.pincode) : ''}${c.territory ? ' · ' + esc(c.territory) : ''}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.primary_contact_phone ? `<span onclick=\\\"event.stopPropagation()\\\" style=\\\"display:inline-flex;gap:4px;align-items:center\\\"><a href=\\\"tel:${esc(c.primary_contact_phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(c.primary_contact_phone)}</a><a href=\\\"https://wa.me/91${esc(c.primary_contact_phone.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"font-size:10px;background:#dcfce7;color:#15803d;padding:1px 5px;border-radius:8px;text-decoration:none;font-weight:600\\\">WA</a></span>` : ''}\u001b[39m\n\u001b[31m          ${c.channel ? `<span style=\\\"font-size:10px;background:var(--card);border:1px solid var(--border);border-radius:8px;padding:1px 6px;color:var(--muted)\\\">${esc(c.channel)}</span>` : ''}\u001b[39m\n\u001b[31m          ${c.gstin ? `<span>GST ${esc(c.gstin.slice(0, 6))}…</span>` : ''}\u001b[39m\n\u001b[31m          ${c.recce_count > 0\u001b[39m\n\u001b[31m            ? `<span style=\\\"color:${c.last_recce_date && (Date.now()-new Date(c.last_recce_date).getTime())>90*86400000?'var(--or)':'var(--accent)'}\\\">📸 ${c.recce_count} Recce${c.recce_count !== 1 ? 's' : ''}${c.last_recce_date ? ' · ' + daysAgo(c.last_recce_date) : ''}</span>`\u001b[39m\n\u001b[31m            : activeFilter === 'norecce' ? `<span style=\\\"color:var(--muted);font-size:11px\\\">Never visited</span>` : ''}\u001b[39m\n\u001b[31m          ${isSupervisor() && (c.allocated_to || []).length ? `<span style=\\\"font-size:10px;background:rgba(99,102,241,.15);border:1px solid rgba(99,102,241,.3);border-radius:8px;padding:1px 6px;color:var(--accent)\\\">👤 ${esc((c.allocated_to || []).join(', '))}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${brands || more ? `<div class=\\\"c-row-meta\\\" style=\\\"margin-top:4px\\\"><span class=\\\"brands\\\">${brands}${more}</span></div>` : ''}\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveAllPending() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const list = allCounters.filter(c => c.approval_status === 'pending');\u001b[39m\n\u001b[31m  if (!list.length) return;\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${list.length} pending counter${list.length !== 1 ? 's' : ''}?\\\\nThis sets all to approved immediately.`)) return;\u001b[39m\n\u001b[31m  const ids = list.map(c => c.counter_id);\u001b[39m\n\u001b[31m  const now = new Date().toISOString();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=in.(${ids.join(',')})`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ approval_status: 'approved', approved_by: myId, approved_at: now }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved ${ids.length} counter${ids.length !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Detail screen ─────────────────────────────────────────\u001b[39m\n\u001b[31masync function openDetail(id) {\u001b[39m\n\u001b[31m  showScreen('s-detail');\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = 'Loading…';\u001b[39m\n\u001b[31m  document.getElementById('btn-edit').style.display     = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-allocate').style.display = isSupervisor() ? '' : 'none';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [row]  = await api(`/counter_v?counter_id=eq.${encodeURIComponent(id)}&limit=1`);\u001b[39m\n\u001b[31m    if (!row) throw new Error('Not found');\u001b[39m\n\u001b[31m    detailCounter = row;\u001b[39m\n\u001b[31m    const empLoad = isSupervisor() ? loadEmployees() : Promise.resolve();\u001b[39m\n\u001b[31m    [detailContacts, detailAddresses] = await Promise.all([\u001b[39m\n\u001b[31m      api(`/contact?counter_id=eq.${encodeURIComponent(id)}&order=is_primary.desc,contact_id.asc`),\u001b[39m\n\u001b[31m      api(`/address?counter_id=eq.${encodeURIComponent(id)}&order=is_default.desc,address_id.asc`),\u001b[39m\n\u001b[31m      empLoad,\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Recce history — cross-schema; counter_id populated once Recce PWA uses DB-backed picker\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailRecces = await fetch(`${DB}/submissions?counter_id=eq.${encodeURIComponent(id)}&order=visit_date.desc&limit=200`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    } catch { detailRecces = []; }\u001b[39m\n\u001b[31m    // Brand association history (available after migrate_counters_v19.sql is run)\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailBrands = await api(`/counter_brand?counter_id=eq.${encodeURIComponent(id)}&order=added_at.desc&limit=100`);\u001b[39m\n\u001b[31m    } catch { detailBrands = []; }\u001b[39m\n\u001b[31m    // Current allocation list\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      detailAllocations = await api(`/allocation?counter_id=eq.${encodeURIComponent(id)}&select=employee_id,job_id&order=employee_id.asc&limit=50`);\u001b[39m\n\u001b[31m    } catch { detailAllocations = []; }\u001b[39m\n\u001b[31m    renderDetail();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Detail failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    showScreen('s-list');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderDetail() {\u001b[39m\n\u001b[31m  const c = detailCounter;\u001b[39m\n\u001b[31m  document.getElementById('d-title').textContent = c.display_name;·\u001b[39m\n\u001b[31m  // Identity card\u001b[39m\n\u001b[31m  const idHtml = [\u001b[39m\n\u001b[31m    row('Display', c.display_name),\u001b[39m\n\u001b[31m    row('Legal',   c.legal_name),\u001b[39m\n\u001b[31m    row('Proprietor', c.proprietor_name),\u001b[39m\n\u001b[31m    row('GSTIN',   c.gstin),\u001b[39m\n\u001b[31m    row('PAN',     c.pan),\u001b[39m\n\u001b[31m    row('Counter type', c.counter_type),\u001b[39m\n\u001b[31m    row('Channel',  c.channel),\u001b[39m\n\u001b[31m    row('Group',    c.customer_group),\u001b[39m\n\u001b[31m    row('Status',  `<span class=\\\"status-chip ${!c.is_active ? 'inactive' : c.status}\\\">${!c.is_active ? 'INACTIVE' : c.status}</span>`),\u001b[39m\n\u001b[31m    row('Approval', c.approval_status === 'pending' ? '<span class=\\\"status-chip pending_review\\\">PENDING REVIEW</span>' : '✓ Approved'),\u001b[39m\n\u001b[31m    c.aliases?.length ? row('Aliases', c.aliases.join(', ')) : '',\u001b[39m\n\u001b[31m    row('Owner',   c.owner_user),\u001b[39m\n\u001b[31m    row('Recce visits', c.recce_count > 0\u001b[39m\n\u001b[31m      ? `${c.recce_count}${c.last_recce_date ? ' · last ' + daysAgo(c.last_recce_date) + ' <span style=\\\"color:var(--muted);font-size:10px\\\">(' + fmtDate(c.last_recce_date) + ')</span>' : ''}`\u001b[39m\n\u001b[31m      : '<span style=\\\"color:var(--muted)\\\">None yet</span>'),\u001b[39m\n\u001b[31m    detailAllocations.length\u001b[39m\n\u001b[31m      ? row('Allocated to', detailAllocations.map(a => {\u001b[39m\n\u001b[31m          const emp = _allEmps.find(e => e.id === a.employee_id);\u001b[39m\n\u001b[31m          const nm = emp ? esc(emp.name) : esc(a.employee_id);\u001b[39m\n\u001b[31m          return nm + (a.job_id ? ` <span style=\\\"color:var(--muted);font-size:10px\\\">(${esc(a.job_id)})</span>` : '');\u001b[39m\n\u001b[31m        }).join(', '))\u001b[39m\n\u001b[31m      : isSupervisor() ? row('Allocated to', '<span style=\\\"color:var(--muted)\\\">Nobody — tap 👥 Allocate to assign</span>') : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  // Primary contact quick-actions row (phone + WhatsApp) — if a primary contact exists\u001b[39m\n\u001b[31m  const primaryContact = detailContacts.find(ct => ct.is_primary) || detailContacts[0];\u001b[39m\n\u001b[31m  const ctPhone = primaryContact?.phone?.replace(/\\\\D/g, '').slice(-10) || '';\u001b[39m\n\u001b[31m  const ctName  = primaryContact?.name || '';\u001b[39m\n\u001b[31m  const contactActionBar = ctPhone ? `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;gap:8px;margin-top:10px;padding-top:10px;border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;color:var(--muted);font-weight:600;align-self:center;flex:1\\\">${esc(ctName)}</div>\u001b[39m\n\u001b[31m      <a href=\\\"tel:${esc(ctPhone)}\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:var(--card);border:1px solid var(--border);border-radius:8px;text-decoration:none;color:var(--text);font-size:12px;font-weight:700;flex-shrink:0\\\">☎ Call</a>\u001b[39m\n\u001b[31m      <a href=\\\"https://wa.me/91${esc(ctPhone)}\\\" target=\\\"_blank\\\" style=\\\"display:flex;align-items:center;gap:4px;padding:6px 12px;background:#dcfce7;border:1px solid #86efac;border-radius:8px;text-decoration:none;color:#14532d;font-size:12px;font-weight:700;flex-shrink:0\\\">💬 WA</a>\u001b[39m\n\u001b[31m    </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-identity').innerHTML = idHtml + contactActionBar;·\u001b[39m\n\u001b[31m  // Address card\u001b[39m\n\u001b[31m  const addrParts = [c.address_line1, c.address_line2, c.city, c.state, c.pincode].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m  const mapsUrl = (c.gps_lat && c.gps_lng)\u001b[39m\n\u001b[31m    ? `https://www.google.com/maps?q=${c.gps_lat},${c.gps_lng}`\u001b[39m\n\u001b[31m    : addrParts ? `https://www.google.com/maps/search/?api=1&query=${encodeURIComponent(addrParts)}` : '';\u001b[39m\n\u001b[31m  const mapsLink = mapsUrl ? ` · <a href=\\\"${mapsUrl}\\\" target=\\\"_blank\\\" style=\\\"color:#7dd3fc\\\">🗺 Map</a>` : '';\u001b[39m\n\u001b[31m  const addrHtml = [\u001b[39m\n\u001b[31m    `<div style=\\\"font-size:13px;font-weight:600\\\">${esc(addrParts)}${mapsLink}</div>`,\u001b[39m\n\u001b[31m    c.country && c.country !== 'India' ? `<div style=\\\"font-size:11px;color:var(--muted);margin-top:2px\\\">${esc(c.country)}</div>` : '',\u001b[39m\n\u001b[31m    c.distributor_name ? row('Distributor', c.distributor_name) : '',\u001b[39m\n\u001b[31m    c.territory ? row('Territory', c.territory) : '',\u001b[39m\n\u001b[31m    c.gps_lat && c.gps_lng ? row('GPS', `<span style=\\\"font-size:11px;font-family:monospace\\\">${esc(c.gps_lat)}, ${esc(c.gps_lng)}</span>`) : '',\u001b[39m\n\u001b[31m  ].filter(Boolean).join('');\u001b[39m\n\u001b[31m  document.getElementById('d-address').innerHTML = addrHtml;\u001b[39m\n\u001b[31m  // Notes card (separate section below address)\u001b[39m\n\u001b[31m  const notesEl = document.getElementById('d-notes');\u001b[39m\n\u001b[31m  if (notesEl) {\u001b[39m\n\u001b[31m    if (c.notes) {\u001b[39m\n\u001b[31m      notesEl.style.display = '';\u001b[39m\n\u001b[31m      notesEl.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.4px;font-weight:700;margin-bottom:6px\\\">Notes</div>\u001b[39m\n\u001b[31m        <div style=\\\"font-size:13px;white-space:pre-wrap;line-height:1.5\\\">${esc(c.notes)}</div>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      notesEl.style.display = 'none';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // Brands tab — combine brand_codes[] with counter_brand history when available\u001b[39m\n\u001b[31m  const brands = c.brand_codes || [];\u001b[39m\n\u001b[31m  const brandsHtml = brands.length\u001b[39m\n\u001b[31m    ? brands.map(b => {\u001b[39m\n\u001b[31m        const hist = detailBrands.filter(r => r.brand_code === b);\u001b[39m\n\u001b[31m        const first = hist.length ? hist[hist.length - 1] : null;\u001b[39m\n\u001b[31m        const last  = hist.length ? hist[0] : null;\u001b[39m\n\u001b[31m        const histLine = first\u001b[39m\n\u001b[31m          ? `Added ${fmtDate(first.added_at)} by ${esc(first.added_by || '—')}${hist.length > 1 ? ` · ${hist.length} entries` : ''}`\u001b[39m\n\u001b[31m          : brandStatsLine(b);\u001b[39m\n\u001b[31m        return `<div class=\\\"k-row\\\"><div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-title\\\">${esc(b)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"k-meta\\\"><span>${histLine}</span>${last && last !== first ? `<span>Last: ${fmtDate(last.added_at)}</span>` : ''}</div>\u001b[39m\n\u001b[31m        </div></div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No brands recorded yet</div></div>`;\u001b[39m\n\u001b[31m  // ADR-143 — master first, then whatever the data already carries\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort();\u001b[39m\n\u001b[31m  const brandDatalist = knownBrands.length\u001b[39m\n\u001b[31m    ? `<datalist id=\\\"brand-suggestions\\\">${knownBrands.map(b => `<option value=\\\"${esc(b)}\\\">`).join('')}</datalist>` : '';\u001b[39m\n\u001b[31m  const addBrandBtn = isSupervisor()\u001b[39m\n\u001b[31m    ? `${brandDatalist}<div style=\\\"margin-top:10px;display:flex;gap:6px;align-items:center\\\" id=\\\"brand-add-row\\\">\u001b[39m\n\u001b[31m        <input id=\\\"brand-add-inp\\\" list=\\\"brand-suggestions\\\" placeholder=\\\"Add brand code…\\\"\u001b[39m\n\u001b[31m          style=\\\"flex:1;padding:7px 10px;border:1.5px solid var(--border);border-radius:8px;font-family:'DM Sans',sans-serif;font-size:13px;background:var(--card);color:var(--text)\\\"\u001b[39m\n\u001b[31m          onkeydown=\\\"if(event.key==='Enter'){event.preventDefault();saveBrandToCounter('${esc(c.counter_id)}')}\\\"\u001b[39m\n\u001b[31m          oninput=\\\"this.value=this.value.toUpperCase().replace(/[^A-Z0-9_]/g,'')\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"saveBrandToCounter('${esc(c.counter_id)}')\\\" style=\\\"padding:7px 14px;font-size:12px\\\">+ Add</button>\u001b[39m\n\u001b[31m       </div>` : '';\u001b[39m\n\u001b[31m  document.getElementById('d-brands').innerHTML = brandsHtml + addBrandBtn;·\u001b[39m\n\u001b[31m  // Contacts tab\u001b[39m\n\u001b[31m  const contactsHtml = detailContacts.length\u001b[39m\n\u001b[31m    ? detailContacts.map(ct => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(ct.name)}${ct.is_primary ? ' <span class=\\\"k-badge primary\\\">PRIMARY</span>' : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${ct.role}\\\">${esc(ct.role)}</span>\u001b[39m\n\u001b[31m              ${ct.designation ? `<span>${esc(ct.designation)}</span>` : ''}\u001b[39m\n\u001b[31m              ${ct.phone ? `<span><a href=\\\"tel:${esc(ct.phone.replace(/\\\\D/g,''))}\\\" style=\\\"color:inherit;text-decoration:none\\\">☎ ${esc(ct.phone)}</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.whatsapp ? `<span><a href=\\\"https://wa.me/91${esc(ct.whatsapp.replace(/\\\\D/g,'').slice(-10))}\\\" target=\\\"_blank\\\" style=\\\"color:#15803d;text-decoration:none\\\">💬 WA</a></span>` : ''}\u001b[39m\n\u001b[31m              ${ct.email ? `<span>✉ ${esc(ct.email)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openContact(${ct.contact_id})\\\">Edit</button><button onclick=\\\"deleteContact(${ct.contact_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📇</div><div>No contacts yet</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').innerHTML = contactsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openContact()\\\" style=\\\"margin-top:6px\\\">+ Add contact</button>` : '');·\u001b[39m\n\u001b[31m  // Secondary addresses tab\u001b[39m\n\u001b[31m  const addrsHtml = detailAddresses.length\u001b[39m\n\u001b[31m    ? detailAddresses.map(a => `\u001b[39m\n\u001b[31m        <div class=\\\"k-row\\\">\u001b[39m\n\u001b[31m          <div class=\\\"k-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"k-title\\\">${esc(a.line1)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"k-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"k-badge ${a.type}\\\">${esc(a.type)}</span>\u001b[39m\n\u001b[31m              ${a.is_default ? '<span class=\\\"k-badge default\\\">DEFAULT</span>' : ''}\u001b[39m\n\u001b[31m              ${a.city ? `<span>${esc(a.city)}</span>` : ''}\u001b[39m\n\u001b[31m              ${a.pincode ? `<span>${esc(a.pincode)}</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${isSupervisor() ? `<div class=\\\"k-actions\\\"><button onclick=\\\"openAddress(${a.address_id})\\\">Edit</button><button onclick=\\\"deleteAddress(${a.address_id})\\\">×</button></div>` : ''}\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📍</div><div>No secondary addresses</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').innerHTML = addrsHtml +\u001b[39m\n\u001b[31m    (isSupervisor() ? `<button class=\\\"btn btn-s\\\" onclick=\\\"openAddress()\\\" style=\\\"margin-top:6px\\\">+ Add address</button>` : '');·\u001b[39m\n\u001b[31m  // Past Recces tab — filter state reset + render\u001b[39m\n\u001b[31m  _recceFilter = { brands: new Set(), campaigns: new Set(), from: '', to: '' };\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveBrandToCounter(counterId) {\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-add-inp');\u001b[39m\n\u001b[31m  if (!inp) return;\u001b[39m\n\u001b[31m  const code = inp.value.trim().toUpperCase().replace(/[^A-Z0-9_]/g, '');\u001b[39m\n\u001b[31m  if (!code) { toast('Enter a brand code', 'err'); inp.focus(); return; }\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  if (existing.has(code)) { toast(`${code} already on this counter`, 'info'); return; }\u001b[39m\n\u001b[31m  const merged = [...existing, code];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/counter?counter_id=eq.${encodeURIComponent(counterId)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({ brand_codes: merged }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify([{ counter_id: counterId, brand_code: code, source: 'manual', added_by: myId }]),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    toast(`${code} added ✓`, 'ok');\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m    await openDetail(counterId);\u001b[39m\n\u001b[31m    setTab('brands');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandStatsLine(brand) {\u001b[39m\n\u001b[31m  const recsForBrand = detailRecces.filter(r => (r.brand || '').toUpperCase() === brand.toUpperCase());\u001b[39m\n\u001b[31m  if (!recsForBrand.length) return 'No past Recce yet';\u001b[39m\n\u001b[31m  const last = recsForBrand[0];\u001b[39m\n\u001b[31m  return `${recsForBrand.length} past Recce${recsForBrand.length > 1 ? 's' : ''} · last ${fmtDate(last.visit_date)}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction row(label, val) {\u001b[39m\n\u001b[31m  if (val == null || val === '') return '';\u001b[39m\n\u001b[31m  return `<div style=\\\"display:flex;justify-content:space-between;gap:8px;font-size:12.5px;padding:3px 0\\\"><span style=\\\"color:var(--muted)\\\">${esc(label)}</span><span style=\\\"font-weight:600;text-align:right\\\">${val}</span></div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setTab(tab) {\u001b[39m\n\u001b[31m  document.querySelectorAll('.tabs button').forEach(b => b.classList.toggle('sel', b.dataset.tab === tab));\u001b[39m\n\u001b[31m  document.getElementById('d-brands').style.display   = tab === 'brands'   ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-contacts').style.display = tab === 'contacts' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-addrs').style.display    = tab === 'addrs'    ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('d-recces').style.display   = tab === 'recces'   ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New / Edit ────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNew(prefill) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) { toast('Supervisor access required', 'err'); return; }\u001b[39m\n\u001b[31m  editingId = null;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'New Counter';\u001b[39m\n\u001b[31m  clearForm();\u001b[39m\n\u001b[31m  if (prefill) {\u001b[39m\n\u001b[31m    if (prefill.name)    document.getElementById('f-display_name').value = prefill.name;\u001b[39m\n\u001b[31m    if (prefill.address) document.getElementById('f-address_line1').value = prefill.address;\u001b[39m\n\u001b[31m    if (prefill.city)    document.getElementById('f-city').value = prefill.city;\u001b[39m\n\u001b[31m    // mobile: put in notes since contacts are created separately\u001b[39m\n\u001b[31m    if (prefill.mobile || prefill.notes) {\u001b[39m\n\u001b[31m      const notesParts = [];\u001b[39m\n\u001b[31m      if (prefill.mobile) notesParts.push('Contact: ' + prefill.mobile);\u001b[39m\n\u001b[31m      if (prefill.notes) notesParts.push(prefill.notes);\u001b[39m\n\u001b[31m      document.getElementById('f-notes').value = notesParts.join('\\\\n');\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  if (prefill?.name) dedupCheck();\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  document.getElementById('f-title').textContent = 'Edit Counter';\u001b[39m\n\u001b[31m  fillForm(detailCounter);\u001b[39m\n\u001b[31m  document.getElementById('dedup').classList.remove('show');\u001b[39m\n\u001b[31m  showScreen('s-form');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction clearForm() {\u001b[39m\n\u001b[31m  ['f-display_name','f-legal_name','f-proprietor_name','f-aliases','f-address_line1','f-address_line2','f-city','f-state','f-state_code','f-pincode','f-gps_lat','f-gps_lng','f-gstin','f-pan','f-fssai','f-drug_lic','f-counter_type','f-channel','f-customer_group','f-territory','f-distributor_name','f-notes','f-city_tier','f-store_type','f-geo_cluster']\u001b[39m\n\u001b[31m    .forEach(id => document.getElementById(id).value = '');\u001b[39m\n\u001b[31m  document.getElementById('f-status').value = 'active';\u001b[39m\n\u001b[31m  brandSet = new Set();\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillForm(c) {\u001b[39m\n\u001b[31m  const set = (id, v) => document.getElementById(id).value = (v == null ? '' : v);\u001b[39m\n\u001b[31m  set('f-display_name',     c.display_name);\u001b[39m\n\u001b[31m  set('f-legal_name',       c.legal_name);\u001b[39m\n\u001b[31m  set('f-proprietor_name',  c.proprietor_name);\u001b[39m\n\u001b[31m  set('f-aliases',          (c.aliases || []).join(', '));\u001b[39m\n\u001b[31m  set('f-address_line1',    c.address_line1);\u001b[39m\n\u001b[31m  set('f-address_line2',    c.address_line2);\u001b[39m\n\u001b[31m  set('f-city',             c.city);\u001b[39m\n\u001b[31m  set('f-state',            c.state);\u001b[39m\n\u001b[31m  set('f-state_code',       c.state_code);\u001b[39m\n\u001b[31m  set('f-pincode',          c.pincode);\u001b[39m\n\u001b[31m  set('f-gps_lat',          c.gps_lat);\u001b[39m\n\u001b[31m  set('f-gps_lng',          c.gps_lng);\u001b[39m\n\u001b[31m  set('f-gstin',            c.gstin);\u001b[39m\n\u001b[31m  set('f-pan',              c.pan);\u001b[39m\n\u001b[31m  set('f-fssai',            c.fssai);\u001b[39m\n\u001b[31m  set('f-drug_lic',         c.drug_lic);\u001b[39m\n\u001b[31m  set('f-counter_type',     c.counter_type || '');\u001b[39m\n\u001b[31m  set('f-channel',          c.channel);\u001b[39m\n\u001b[31m  set('f-customer_group',   c.customer_group);\u001b[39m\n\u001b[31m  set('f-territory',        c.territory);\u001b[39m\n\u001b[31m  set('f-city_tier',        c.city_tier  || '');\u001b[39m\n\u001b[31m  set('f-store_type',       c.store_type || '');\u001b[39m\n\u001b[31m  set('f-geo_cluster',      c.geo_cluster  || '');\u001b[39m\n\u001b[31m  set('f-distributor_name', c.distributor_name);\u001b[39m\n\u001b[31m  set('f-status',           c.status || 'active');\u001b[39m\n\u001b[31m  set('f-notes',            c.notes);\u001b[39m\n\u001b[31m  brandSet = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m  renderBrandChips();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cancelForm() {\u001b[39m\n\u001b[31m  if (editingId) showScreen('s-detail'); else showScreen('s-list');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillGpsFromBrowser() {\u001b[39m\n\u001b[31m  const st = document.getElementById('gps-status');\u001b[39m\n\u001b[31m  if (!navigator.geolocation) { if (st) st.textContent = 'Geolocation not supported by this browser.'; return; }\u001b[39m\n\u001b[31m  if (st) st.textContent = '⏳ Getting location…';\u001b[39m\n\u001b[31m  navigator.geolocation.getCurrentPosition(\u001b[39m\n\u001b[31m    pos => {\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lat').value = pos.coords.latitude.toFixed(7);\u001b[39m\n\u001b[31m      document.getElementById('f-gps_lng').value = pos.coords.longitude.toFixed(7);\u001b[39m\n\u001b[31m      if (st) st.textContent = `✓ Accuracy ±${Math.round(pos.coords.accuracy)}m`;\u001b[39m\n\u001b[31m    },\u001b[39m\n\u001b[31m    err => { if (st) st.textContent = 'Location error: ' + err.message; },\u001b[39m\n\u001b[31m    { enableHighAccuracy: true, timeout: 10000 }\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction brandKey(ev) {\u001b[39m\n\u001b[31m  const inp = ev.target;\u001b[39m\n\u001b[31m  if (ev.key === 'Enter' || ev.key === ',') {\u001b[39m\n\u001b[31m    ev.preventDefault();\u001b[39m\n\u001b[31m    const v = inp.value.trim().toUpperCase();\u001b[39m\n\u001b[31m    if (v) { brandSet.add(v); renderBrandChips(); }\u001b[39m\n\u001b[31m    inp.value = '';\u001b[39m\n\u001b[31m  } else if (ev.key === 'Backspace' && !inp.value && brandSet.size) {\u001b[39m\n\u001b[31m    const arr = [...brandSet]; arr.pop(); brandSet = new Set(arr); renderBrandChips();\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderBrandChips() {\u001b[39m\n\u001b[31m  const box = document.getElementById('bcset');\u001b[39m\n\u001b[31m  // remove existing chips (keep the input)\u001b[39m\n\u001b[31m  box.querySelectorAll('.b').forEach(b => b.remove());\u001b[39m\n\u001b[31m  const inp = document.getElementById('brand-inp');\u001b[39m\n\u001b[31m  [...brandSet].forEach(b => {\u001b[39m\n\u001b[31m    const el = document.createElement('span');\u001b[39m\n\u001b[31m    el.className = 'b';\u001b[39m\n\u001b[31m    el.innerHTML = `${esc(b)}<button type=\\\"button\\\" aria-label=\\\"Remove\\\">×</button>`;\u001b[39m\n\u001b[31m    el.querySelector('button').addEventListener('click', () => { brandSet.delete(b); renderBrandChips(); });\u001b[39m\n\u001b[31m    box.insertBefore(el, inp);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction pincodeAutoFill(pin) {\u001b[39m\n\u001b[31m  if (!pin || pin.length < 6) return;\u001b[39m\n\u001b[31m  const cityEl  = document.getElementById('f-city');\u001b[39m\n\u001b[31m  const stateEl = document.getElementById('f-state');\u001b[39m\n\u001b[31m  // Only auto-fill if fields are currently empty\u001b[39m\n\u001b[31m  if (cityEl.value.trim() && stateEl.value.trim()) return;\u001b[39m\n\u001b[31m  const match = allCounters.find(c => c.pincode === pin && c.city);\u001b[39m\n\u001b[31m  if (!match) return;\u001b[39m\n\u001b[31m  if (!cityEl.value.trim() && match.city)   { cityEl.value  = match.city;  }\u001b[39m\n\u001b[31m  if (!stateEl.value.trim() && match.state) { stateEl.value = match.state; }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction dedupCheck() {\u001b[39m\n\u001b[31m  if (editingId) return; // dedup only for new counters\u001b[39m\n\u001b[31m  clearTimeout(_dedupTimer);\u001b[39m\n\u001b[31m  _dedupTimer = setTimeout(() => {\u001b[39m\n\u001b[31m    const name = (document.getElementById('f-display_name').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const pincode = (document.getElementById('f-pincode').value || '').trim();\u001b[39m\n\u001b[31m    const city    = (document.getElementById('f-city').value || '').trim().toLowerCase();\u001b[39m\n\u001b[31m    const gstin   = (document.getElementById('f-gstin').value || '').trim();\u001b[39m\n\u001b[31m    if (name.length < 3) { document.getElementById('dedup').classList.remove('show'); return; }\u001b[39m\n\u001b[31m    const matches = allCounters.filter(c => {\u001b[39m\n\u001b[31m      if (gstin && c.gstin && c.gstin.toUpperCase() === gstin.toUpperCase()) return true;\u001b[39m\n\u001b[31m      const cName = (c.display_name || '').toLowerCase();\u001b[39m\n\u001b[31m      const sameRegion = (pincode && c.pincode === pincode) || (city && (c.city || '').toLowerCase() === city);\u001b[39m\n\u001b[31m      if (!sameRegion) return false;\u001b[39m\n\u001b[31m      return cName.includes(name) || name.includes(cName) || levenshtein(name, cName) <= 3;\u001b[39m\n\u001b[31m    }).slice(0, 5);\u001b[39m\n\u001b[31m    _dedupMatches = matches;\u001b[39m\n\u001b[31m    const panel = document.getElementById('dedup');\u001b[39m\n\u001b[31m    if (!matches.length) { panel.classList.remove('show'); return; }\u001b[39m\n\u001b[31m    document.getElementById('dedup-list').innerHTML = matches.map(m => `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:6px;font-size:12px\\\">\u001b[39m\n\u001b[31m        <b>${esc(m.display_name)}</b>${m.city ? ' · ' + esc(m.city) : ''}${m.pincode ? ' · ' + esc(m.pincode) : ''}\u001b[39m\n\u001b[31m        <button onclick=\\\"openDetail('${esc(m.counter_id)}')\\\">Open</button>\u001b[39m\n\u001b[31m      </div>`).join('');\u001b[39m\n\u001b[31m    panel.classList.add('show');\u001b[39m\n\u001b[31m  }, 250);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveForm() {\u001b[39m\n\u001b[31m  const display_name = document.getElementById('f-display_name').value.trim();\u001b[39m\n\u001b[31m  const address_line1 = document.getElementById('f-address_line1').value.trim();\u001b[39m\n\u001b[31m  if (!display_name) { toast('Display name is required', 'err'); return; }\u001b[39m\n\u001b[31m  if (!address_line1) { toast('Address line 1 is required', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-save');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.innerHTML = '<span class=\\\"spin\\\">↻</span> Saving';\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    display_name,\u001b[39m\n\u001b[31m    legal_name:       trim('f-legal_name'),\u001b[39m\n\u001b[31m    proprietor_name:  trim('f-proprietor_name'),\u001b[39m\n\u001b[31m    aliases:          (document.getElementById('f-aliases').value || '').split(',').map(s => s.trim()).filter(Boolean),\u001b[39m\n\u001b[31m    address_line1,\u001b[39m\n\u001b[31m    address_line2:    trim('f-address_line2'),\u001b[39m\n\u001b[31m    city:             trim('f-city'),\u001b[39m\n\u001b[31m    state:            trim('f-state'),\u001b[39m\n\u001b[31m    state_code:       trim('f-state_code'),\u001b[39m\n\u001b[31m    pincode:          trim('f-pincode'),\u001b[39m\n\u001b[31m    gps_lat:          numOrNull('f-gps_lat'),\u001b[39m\n\u001b[31m    gps_lng:          numOrNull('f-gps_lng'),\u001b[39m\n\u001b[31m    gstin:            (trim('f-gstin') || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    pan:              (trim('f-pan')   || '').toUpperCase() || null,\u001b[39m\n\u001b[31m    fssai:            trim('f-fssai'),\u001b[39m\n\u001b[31m    drug_lic:         trim('f-drug_lic'),\u001b[39m\n\u001b[31m    counter_type:     document.getElementById('f-counter_type').value || null,\u001b[39m\n\u001b[31m    channel:          trim('f-channel'),\u001b[39m\n\u001b[31m    city_tier:        document.getElementById('f-city_tier').value  || null,\u001b[39m\n\u001b[31m    store_type:       document.getElementById('f-store_type').value || null,\u001b[39m\n\u001b[31m    geo_cluster:      trim('f-geo_cluster') || null,\u001b[39m\n\u001b[31m    customer_group:   trim('f-customer_group'),\u001b[39m\n\u001b[31m    territory:        trim('f-territory'),\u001b[39m\n\u001b[31m    distributor_name: trim('f-distributor_name'),\u001b[39m\n\u001b[31m    brand_codes:      [...brandSet],\u001b[39m\n\u001b[31m    status:           document.getElementById('f-status').value,\u001b[39m\n\u001b[31m    notes:            trim('f-notes'),\u001b[39m\n\u001b[31m    updated_by:       myId,\u001b[39m\n\u001b[31m    updated_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (editingId) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(editingId)}`, {\u001b[39m\n\u001b[31m        method:  'PATCH',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      const fresh = allCounters.find(c => c.counter_id === editingId);\u001b[39m\n\u001b[31m      if (fresh) { detailCounter = fresh; await openDetail(editingId); }\u001b[39m\n\u001b[31m      else showScreen('s-list');\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      payload.counter_id   = newId(display_name);\u001b[39m\n\u001b[31m      payload.created_by   = myId;\u001b[39m\n\u001b[31m      payload.created_from = 'manual';\u001b[39m\n\u001b[31m      await api('/counter', {\u001b[39m\n\u001b[31m        method:  'POST',\u001b[39m\n\u001b[31m        body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      toast('Created ✓', 'ok');\u001b[39m\n\u001b[31m      await loadList();\u001b[39m\n\u001b[31m      await openDetail(payload.counter_id);\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.innerHTML = 'Save';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction trim(id)       { return (document.getElementById(id).value || '').trim() || null; }\u001b[39m\n\u001b[31mfunction numOrNull(id)  { const v = document.getElementById(id).value; return v === '' ? null : Number(v); }·\u001b[39m\n\u001b[31m// ── Contacts ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openContact(contactId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingContact = contactId || null;\u001b[39m\n\u001b[31m  const ct = contactId ? detailContacts.find(c => c.contact_id === contactId) : {};\u001b[39m\n\u001b[31m  document.getElementById('contact-ttl').textContent = contactId ? 'Edit contact' : 'Add contact';\u001b[39m\n\u001b[31m  document.getElementById('cf-name').value         = ct?.name         || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-role').value         = ct?.role         || 'primary';\u001b[39m\n\u001b[31m  document.getElementById('cf-designation').value  = ct?.designation  || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-phone').value        = ct?.phone        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-whatsapp').value     = ct?.whatsapp     || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-email').value        = ct?.email        || '';\u001b[39m\n\u001b[31m  document.getElementById('cf-is_primary').checked = !!ct?.is_primary;\u001b[39m\n\u001b[31m  document.getElementById('modal-contact').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveContact() {\u001b[39m\n\u001b[31m  const name = document.getElementById('cf-name').value.trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id:  detailCounter.counter_id,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    role:        document.getElementById('cf-role').value,\u001b[39m\n\u001b[31m    designation: document.getElementById('cf-designation').value.trim() || null,\u001b[39m\n\u001b[31m    phone:       document.getElementById('cf-phone').value.trim() || null,\u001b[39m\n\u001b[31m    whatsapp:    document.getElementById('cf-whatsapp').value.trim() || null,\u001b[39m\n\u001b[31m    email:       document.getElementById('cf-email').value.trim() || null,\u001b[39m\n\u001b[31m    is_primary:  document.getElementById('cf-is_primary').checked,\u001b[39m\n\u001b[31m    updated_at:  new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // demote other primaries first if we're making this one primary\u001b[39m\n\u001b[31m    if (payload.is_primary) {\u001b[39m\n\u001b[31m      await api(`/contact?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_primary=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_primary: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingContact) {\u001b[39m\n\u001b[31m      await api(`/contact?contact_id=eq.${editingContact}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/contact', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-contact');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Save failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteContact(contactId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this contact?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/contact?contact_id=eq.${contactId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Secondary addresses ───────────────────────────────────\u001b[39m\n\u001b[31mfunction openAddress(addressId) {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  editingAddress = addressId || null;\u001b[39m\n\u001b[31m  const a = addressId ? detailAddresses.find(x => x.address_id === addressId) : {};\u001b[39m\n\u001b[31m  document.getElementById('address-ttl').textContent = addressId ? 'Edit address' : 'Add address';\u001b[39m\n\u001b[31m  document.getElementById('af-type').value       = a?.type       || 'billing';\u001b[39m\n\u001b[31m  document.getElementById('af-line1').value      = a?.line1      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-line2').value      = a?.line2      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-city').value       = a?.city       || '';\u001b[39m\n\u001b[31m  document.getElementById('af-state').value      = a?.state      || '';\u001b[39m\n\u001b[31m  document.getElementById('af-pincode').value    = a?.pincode    || '';\u001b[39m\n\u001b[31m  document.getElementById('af-is_default').checked = !!a?.is_default;\u001b[39m\n\u001b[31m  document.getElementById('modal-address').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAddress() {\u001b[39m\n\u001b[31m  const line1 = document.getElementById('af-line1').value.trim();\u001b[39m\n\u001b[31m  if (!line1) { toast('Line 1 required', 'err'); return; }\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    counter_id: detailCounter.counter_id,\u001b[39m\n\u001b[31m    type:       document.getElementById('af-type').value,\u001b[39m\n\u001b[31m    line1,\u001b[39m\n\u001b[31m    line2:      document.getElementById('af-line2').value.trim() || null,\u001b[39m\n\u001b[31m    city:       document.getElementById('af-city').value.trim() || null,\u001b[39m\n\u001b[31m    state:      document.getElementById('af-state').value.trim() || null,\u001b[39m\n\u001b[31m    pincode:    document.getElementById('af-pincode').value.trim() || null,\u001b[39m\n\u001b[31m    is_default: document.getElementById('af-is_default').checked,\u001b[39m\n\u001b[31m    updated_at: new Date().toISOString(),\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (payload.is_default) {\u001b[39m\n\u001b[31m      await api(`/address?counter_id=eq.${encodeURIComponent(payload.counter_id)}&is_default=eq.true`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify({ is_default: false }), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (editingAddress) {\u001b[39m\n\u001b[31m      await api(`/address?address_id=eq.${editingAddress}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      await api('/address', { method: 'POST', body: JSON.stringify(payload), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-address');\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Saved ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Save failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function deleteAddress(addressId) {\u001b[39m\n\u001b[31m  if (!confirm('Delete this address?')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/address?address_id=eq.${addressId}`, { method: 'DELETE', headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    await openDetail(detailCounter.counter_id);\u001b[39m\n\u001b[31m    toast('Deleted', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Delete failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeModal(id) { document.getElementById(id).classList.remove('show'); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — ALLOCATION (per-employee visibility)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _allEmps    = [];   // active employees from expense.employees\u001b[39m\n\u001b[31mlet _allocSet   = new Set();    // selected emp_ids in the modal\u001b[39m\n\u001b[31mlet _existingAlloc = new Map(); // emp_id → allocation_id for current counter\u001b[39m\n\u001b[31mlet _lastCoverageShareText = ''; // last generated coverage report share text·\u001b[39m\n\u001b[31masync function shareCoverageReport() {\u001b[39m\n\u001b[31m  const text = _lastCoverageShareText;\u001b[39m\n\u001b[31m  if (!text) { toast('Open Coverage Report first', 'info'); return; }\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await navigator.clipboard.writeText(text);\u001b[39m\n\u001b[31m    toast('Coverage summary copied! Paste into WhatsApp.', 'success');\u001b[39m\n\u001b[31m  } catch {\u001b[39m\n\u001b[31m    // Clipboard API blocked — open WhatsApp directly\u001b[39m\n\u001b[31m    window.open('https://wa.me/?text=' + encodeURIComponent(text), '_blank');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function quickAllocate(counterId) {\u001b[39m\n\u001b[31m  // Open the allocation screen for a counter directly from the list (no need to open detail first)\u001b[39m\n\u001b[31m  const c = allCounters.find(x => x.counter_id === counterId);\u001b[39m\n\u001b[31m  if (!c) return;\u001b[39m\n\u001b[31m  detailCounter = c;\u001b[39m\n\u001b[31m  await openAllocate();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction openBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  // Compute scope from current filtered list\u001b[39m\n\u001b[31m  const filtered = _filteredCounters();\u001b[39m\n\u001b[31m  document.getElementById('bulk-scope-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-apply-count').textContent = filtered.length;\u001b[39m\n\u001b[31m  document.getElementById('bulk-brand').value       = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-counter-type').value = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-territory').value   = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-channel').value     = '';\u001b[39m\n\u001b[31m  document.getElementById('bulk-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-bulk-apply').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('modal-bulk-edit').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Returns the counters currently visible in the list (same as filterCounters())\u001b[39m\n\u001b[31mfunction _filteredCounters() { return filterCounters(); }·\u001b[39m\n\u001b[31masync function runBulkEdit() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  const brandCode   = document.getElementById('bulk-brand').value.trim();\u001b[39m\n\u001b[31m  const counterType = document.getElementById('bulk-counter-type').value;\u001b[39m\n\u001b[31m  const territory   = document.getElementById('bulk-territory').value.trim();\u001b[39m\n\u001b[31m  const channel     = document.getElementById('bulk-channel').value;\u001b[39m\n\u001b[31m  if (!brandCode && !counterType && !territory && !channel) {\u001b[39m\n\u001b[31m    toast('Set at least one field to apply', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const targets = _filteredCounters();\u001b[39m\n\u001b[31m  if (!targets.length) { toast('No counters match the current filter', 'err'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Apply changes to ${targets.length} counter${targets.length !== 1 ? 's' : ''}?\\\\n\\\\nThis will:\\\\n${brandCode ? `• Add brand \\\"${brandCode}\\\" (merge with existing)\\\\n` : ''}${counterType ? `• Set counter type \\\"${counterType}\\\" (blank fields only)\\\\n` : ''}${territory  ? `• Set territory \\\"${territory}\\\" (blank fields only)\\\\n` : ''}${channel    ? `• Set channel \\\"${channel}\\\" (blank fields only)\\\\n` : ''}`)) return;·\u001b[39m\n\u001b[31m  const btn    = document.getElementById('btn-bulk-apply');\u001b[39m\n\u001b[31m  const status = document.getElementById('bulk-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  status.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  status.textContent = `⏳ Updating ${targets.length} counters…`;·\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  const brandAssocRows = [];\u001b[39m\n\u001b[31m  for (const c of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    // Brand merge\u001b[39m\n\u001b[31m    if (brandCode) {\u001b[39m\n\u001b[31m      const existing = new Set(c.brand_codes || []);\u001b[39m\n\u001b[31m      if (!existing.has(brandCode)) {\u001b[39m\n\u001b[31m        patch.brand_codes = [...existing, brandCode];\u001b[39m\n\u001b[31m        brandAssocRows.push({ counter_id: c.counter_id, brand_code: brandCode, source: 'manual', added_by: myId });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Fill-blank fields\u001b[39m\n\u001b[31m    if (counterType && !c.counter_type) patch.counter_type = counterType;\u001b[39m\n\u001b[31m    if (territory  && !c.territory)     patch.territory    = territory;\u001b[39m\n\u001b[31m    if (channel    && !c.channel)       patch.channel      = channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(c.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch (_) { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // Write brand associations\u001b[39m\n\u001b[31m  if (brandAssocRows.length) {\u001b[39m\n\u001b[31m    await api('/counter_brand', {\u001b[39m\n\u001b[31m      method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  status.style.color = fail ? 'var(--err)' : '#4ade80';\u001b[39m\n\u001b[31m  status.textContent = fail\u001b[39m\n\u001b[31m    ? `✗ ${ok} updated, ${fail} failed`\u001b[39m\n\u001b[31m    : `✓ ${ok} counter${ok !== 1 ? 's' : ''} updated successfully`;\u001b[39m\n\u001b[31m  btn.disabled = false;\u001b[39m\n\u001b[31m  if (!fail) {\u001b[39m\n\u001b[31m    toast(`Bulk update done — ${ok} counters`, 'ok');\u001b[39m\n\u001b[31m    setTimeout(() => { closeModal('modal-bulk-edit'); loadList(); }, 1500);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openCoverageReport(days) {\u001b[39m\n\u001b[31m  days = days || 30;\u001b[39m\n\u001b[31m  document.getElementById('modal-coverage').classList.add('show');\u001b[39m\n\u001b[31m  const out = document.getElementById('coverage-out');\u001b[39m\n\u001b[31m  out.innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await loadEmployees();\u001b[39m\n\u001b[31m    const cutoff = new Date(Date.now() - days * 86400000).toISOString().slice(0, 10);\u001b[39m\n\u001b[31m    // Fetch allocations AND recce submissions in parallel\u001b[39m\n\u001b[31m    const [allocs, recces] = await Promise.all([\u001b[39m\n\u001b[31m      api('/allocation?select=employee_id,counter_id&limit=5000'),\u001b[39m\n\u001b[31m      fetch(`${DB}/submissions?visit_date=gte.${cutoff}&select=counter_id,submitted_by,visit_date&limit=5000`, {\u001b[39m\n\u001b[31m        headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m      }).then(r => r.ok ? r.json() : []),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    // Build visit map: employee_id → Set of counter_ids visited in window\u001b[39m\n\u001b[31m    const visitMap = {};    // employee_id → Map<counter_id, max_visit_date>\u001b[39m\n\u001b[31m    recces.forEach(r => {\u001b[39m\n\u001b[31m      if (!r.submitted_by || !r.counter_id) return;\u001b[39m\n\u001b[31m      if (!visitMap[r.submitted_by]) visitMap[r.submitted_by] = {};\u001b[39m\n\u001b[31m      const cur = visitMap[r.submitted_by][r.counter_id];\u001b[39m\n\u001b[31m      if (!cur || r.visit_date > cur) visitMap[r.submitted_by][r.counter_id] = r.visit_date;\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Group allocations by employee\u001b[39m\n\u001b[31m    const byEmp = {};\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      if (!byEmp[a.employee_id]) byEmp[a.employee_id] = new Set();\u001b[39m\n\u001b[31m      byEmp[a.employee_id].add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const rows = _allEmps.map(e => {\u001b[39m\n\u001b[31m      const allocCids = byEmp[e.id] ? [...byEmp[e.id]] : [];\u001b[39m\n\u001b[31m      const empVisits = visitMap[e.id] || {};\u001b[39m\n\u001b[31m      const visitedCids = allocCids.filter(cid => empVisits[cid]);\u001b[39m\n\u001b[31m      const unvisitedCounters = allocCids\u001b[39m\n\u001b[31m        .filter(cid => !empVisits[cid])\u001b[39m\n\u001b[31m        .map(cid => allCounters.find(x => x.counter_id === cid))\u001b[39m\n\u001b[31m        .filter(Boolean);\u001b[39m\n\u001b[31m      // Last visit by this rep across all their allocated counters\u001b[39m\n\u001b[31m      const lastVisit = allocCids.reduce((best, cid) => {\u001b[39m\n\u001b[31m        const d = empVisits[cid] || '';\u001b[39m\n\u001b[31m        return d > best ? d : best;\u001b[39m\n\u001b[31m      }, '');\u001b[39m\n\u001b[31m      return { id: e.id, name: e.name, role: e.role, allocated: allocCids.length, visited: visitedCids.length, lastVisit, unvisited: unvisitedCounters };\u001b[39m\n\u001b[31m    }).sort((a, b) => b.allocated - a.allocated);·\u001b[39m\n\u001b[31m    // Build territory-level coverage: territory → {total allocated counters (unique), visited (any rep)}\u001b[39m\n\u001b[31m    const terrMap = {}; // territory → { alloc: Set<counter_id>, visited: Set<counter_id> }\u001b[39m\n\u001b[31m    allocs.forEach(a => {\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === a.counter_id);\u001b[39m\n\u001b[31m      const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m      if (!terrMap[t]) terrMap[t] = { alloc: new Set(), visited: new Set() };\u001b[39m\n\u001b[31m      terrMap[t].alloc.add(a.counter_id);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Mark visited: any rep visited this counter in the window\u001b[39m\n\u001b[31m    Object.values(visitMap).forEach(empVisits => {\u001b[39m\n\u001b[31m      Object.keys(empVisits).forEach(cid => {\u001b[39m\n\u001b[31m        const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m        const t = (c?.territory || 'Unknown');\u001b[39m\n\u001b[31m        if (terrMap[t]) terrMap[t].visited.add(cid);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const terrRows = Object.entries(terrMap)\u001b[39m\n\u001b[31m      .map(([t, v]) => ({ territory: t, total: v.alloc.size, visited: v.visited.size }))\u001b[39m\n\u001b[31m      .sort((a, b) => b.total - a.total);·\u001b[39m\n\u001b[31m    const totalAllocs = allocs.length;\u001b[39m\n\u001b[31m    const repCount    = Object.keys(byEmp).length;\u001b[39m\n\u001b[31m    const dayLabels = { 30: '30 days', 60: '60 days', 90: '90 days' };\u001b[39m\n\u001b[31m    const dayBtns = [30, 60, 90].map(d =>\u001b[39m\n\u001b[31m      `<button onclick=\\\"openCoverageReport(${d})\\\" style=\\\"padding:3px 10px;border-radius:12px;border:1px solid var(--border);background:${d===days?'var(--accent)':'var(--card)'};color:${d===days?'#fff':'var(--text)'};font-size:11px;font-weight:600;cursor:pointer;font-family:inherit\\\">${dayLabels[d]}</button>`\u001b[39m\n\u001b[31m    ).join('');·\u001b[39m\n\u001b[31m    // Pre-build share text so the button works inline\u001b[39m\n\u001b[31m    const shareLines = ['📊 *Counter Coverage Report*', `Last ${days} days · ${new Date().toLocaleDateString('en-IN', { day:'numeric', month:'short', year:'numeric' })}`, ''];\u001b[39m\n\u001b[31m    rows.filter(r => r.allocated > 0).forEach(r => {\u001b[39m\n\u001b[31m      const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m      const bar = pct === 100 ? '✅' : pct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m      shareLines.push(`${bar} *${r.name}*: ${r.visited}/${r.allocated} (${pct}%)`);\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (terrRows.length) {\u001b[39m\n\u001b[31m      shareLines.push('', '*By Territory:*');\u001b[39m\n\u001b[31m      terrRows.forEach(tr => {\u001b[39m\n\u001b[31m        const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m        const bar = tPct === 100 ? '✅' : tPct >= 50 ? '🟡' : '🔴';\u001b[39m\n\u001b[31m        shareLines.push(`${bar} ${tr.territory}: ${tr.visited}/${tr.total} (${tPct}%)`);\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _lastCoverageShareText = shareLines.join('\\\\n');·\u001b[39m\n\u001b[31m    out.innerHTML = `\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;align-items:center;gap:6px;padding:10px 0 8px;flex-wrap:wrap\\\">\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted)\\\">Window:</span>${dayBtns}\u001b[39m\n\u001b[31m        <span style=\\\"font-size:11px;color:var(--muted);margin-left:auto\\\">${totalAllocs} alloc${totalAllocs !== 1 ? 's' : ''} · ${repCount} rep${repCount !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m        <button onclick=\\\"shareCoverageReport()\\\" title=\\\"Copy summary to clipboard / share via WhatsApp\\\"\u001b[39m\n\u001b[31m          style=\\\"background:none;border:1px solid var(--border);border-radius:8px;padding:3px 8px;font-size:11px;cursor:pointer;color:var(--text);font-family:inherit;display:flex;align-items:center;gap:4px\\\">\u001b[39m\n\u001b[31m          📤 Share\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>` +\u001b[39m\n\u001b[31m      rows.map(r => {\u001b[39m\n\u001b[31m        const pct = r.allocated ? Math.round(r.visited / r.allocated * 100) : 0;\u001b[39m\n\u001b[31m        const clr = pct === 100 ? '#22c55e' : pct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m        const uvHtml = r.unvisited.length\u001b[39m\n\u001b[31m          ? r.unvisited.map(c => {\u001b[39m\n\u001b[31m              const loc = [c.city, c.territory].filter(Boolean).join(', ');\u001b[39m\n\u001b[31m              return `<button onclick=\\\"closeModal('modal-coverage');openDetail('${esc(c.counter_id)}')\\\"\u001b[39m\n\u001b[31m                style=\\\"background:rgba(255,255,255,.07);border:1px solid var(--border);border-radius:8px;padding:4px 10px;font-size:11px;font-weight:600;cursor:pointer;color:var(--fg);font-family:inherit;text-align:left\\\"\u001b[39m\n\u001b[31m                title=\\\"${esc(loc)}\\\">\u001b[39m\n\u001b[31m                ${esc(c.display_name)}${loc ? `<span style=\\\"font-weight:400;color:var(--muted);margin-left:4px\\\">${esc(loc)}</span>` : ''}\u001b[39m\n\u001b[31m              </button>`;}).join('')\u001b[39m\n\u001b[31m          : r.allocated\u001b[39m\n\u001b[31m            ? `<span style=\\\"font-size:11px;color:#22c55e;font-weight:600\\\">✓ All counters visited this ${days === 30 ? 'month' : days + ' days'}</span>`\u001b[39m\n\u001b[31m            : `<span style=\\\"font-size:11px;color:var(--muted)\\\">No allocations</span>`;\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div style=\\\"border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m          <div onclick=\\\"const uv=this.nextElementSibling;uv.style.display=uv.style.display==='flex'?'none':'flex'\\\" style=\\\"display:flex;align-items:center;gap:10px;padding:9px 0;cursor:pointer\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:13px;font-weight:700\\\">${esc(r.name)}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:10.5px;color:var(--muted)\\\">${esc(r.role || '')}${r.lastVisit ? ' · last Recce ' + daysAgo(r.lastVisit) : ` · no Recces in ${days} days`}</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"text-align:right;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"font-size:14px;font-weight:700;color:${clr}\\\">${r.visited}/${r.allocated}</div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9.5px;color:var(--muted)\\\">in ${days}d</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m            <div style=\\\"width:44px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:5px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${pct}%;background:${clr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:center;margin-top:2px\\\">${pct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"display:none;padding:6px 0 10px;flex-wrap:wrap;gap:6px\\\">\u001b[39m\n\u001b[31m            ${uvHtml}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('') +\u001b[39m\n\u001b[31m      (terrRows.length ? `\u001b[39m\n\u001b[31m      <div style=\\\"margin-top:16px;padding-top:12px;border-top:2px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);letter-spacing:.06em;margin-bottom:8px\\\">BY TERRITORY</div>\u001b[39m\n\u001b[31m        ${terrRows.map(tr => {\u001b[39m\n\u001b[31m          const tPct = tr.total ? Math.round(tr.visited / tr.total * 100) : 0;\u001b[39m\n\u001b[31m          const tClr = tPct === 100 ? '#22c55e' : tPct >= 50 ? '#f59e0b' : '#ef4444';\u001b[39m\n\u001b[31m          return `<div style=\\\"display:flex;align-items:center;gap:10px;padding:6px 0;border-bottom:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m            <div style=\\\"flex:1;font-size:12px;font-weight:600\\\">${esc(tr.territory)}</div>\u001b[39m\n\u001b[31m            <div style=\\\"font-size:12px;font-weight:700;color:${tClr};flex-shrink:0\\\">${tr.visited}/${tr.total}</div>\u001b[39m\n\u001b[31m            <div style=\\\"width:60px;flex-shrink:0\\\">\u001b[39m\n\u001b[31m              <div style=\\\"height:4px;background:var(--border);border-radius:3px;overflow:hidden\\\">\u001b[39m\n\u001b[31m                <div style=\\\"height:100%;width:${tPct}%;background:${tClr};border-radius:3px\\\"></div>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m              <div style=\\\"font-size:9px;color:var(--muted);text-align:right;margin-top:1px\\\">${tPct}%</div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>`;\u001b[39m\n\u001b[31m        }).join('')}\u001b[39m\n\u001b[31m      </div>` : '');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadEmployees() {\u001b[39m\n\u001b[31m  if (_allEmps.length) return _allEmps;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _allEmps = await fetch(`${DB}/employees?active=is.true&select=id,name,role&order=name.asc`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'expense', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m  } catch { _allEmps = []; }\u001b[39m\n\u001b[31m  return _allEmps;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openAllocate() {\u001b[39m\n\u001b[31m  if (!isSupervisor() || !detailCounter) return;\u001b[39m\n\u001b[31m  await loadEmployees();\u001b[39m\n\u001b[31m  // Fetch existing allocations for this counter\u001b[39m\n\u001b[31m  let existing = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    existing = await api(`/allocation?counter_id=eq.${encodeURIComponent(detailCounter.counter_id)}&select=allocation_id,employee_id,job_id`);\u001b[39m\n\u001b[31m  } catch (e) { /* ignore */ }\u001b[39m\n\u001b[31m  _existingAlloc = new Map();\u001b[39m\n\u001b[31m  _allocSet = new Set();\u001b[39m\n\u001b[31m  existing.forEach(a => {\u001b[39m\n\u001b[31m    _existingAlloc.set(a.employee_id + '__' + (a.job_id || ''), a.allocation_id);\u001b[39m\n\u001b[31m    _allocSet.add(a.employee_id);\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  document.getElementById('alloc-job_id').value = '';\u001b[39m\n\u001b[31m  document.getElementById('alloc-ttl').textContent = `👥 Who sees ${detailCounter.display_name}?`;\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m  document.getElementById('modal-alloc').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAllocTiles() {\u001b[39m\n\u001b[31m  const activeIds  = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  const total      = _allEmps.length;\u001b[39m\n\u001b[31m  const selectedN  = [..._allocSet].filter(id => activeIds.has(id)).length;\u001b[39m\n\u001b[31m  document.getElementById('alloc-summary').textContent =\u001b[39m\n\u001b[31m    `${selectedN} of ${total} employees · ${selectedN === total ? '✓ All' : selectedN === 0 ? 'Nobody' : 'Custom'}`;\u001b[39m\n\u001b[31m  const tiles = document.getElementById('alloc-tiles');\u001b[39m\n\u001b[31m  const allSel = selectedN === _allEmps.length && _allEmps.length > 0;\u001b[39m\n\u001b[31m  // Inactive employees who still have an allocation (can be unchecked to remove)\u001b[39m\n\u001b[31m  const inactiveAllocated = [..._allocSet].filter(id => !activeIds.has(id));\u001b[39m\n\u001b[31m  const inactiveHtml = inactiveAllocated.length\u001b[39m\n\u001b[31m    ? `<div style=\\\"font-size:10px;color:var(--muted);letter-spacing:.3px;text-transform:uppercase;font-weight:700;padding:8px 0 4px;grid-column:1/-1\\\">Former team (inactive)</div>` +\u001b[39m\n\u001b[31m      inactiveAllocated.map(id => `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile sel\\\" style=\\\"opacity:.6\\\" onclick=\\\"toggleAlloc('${esc(id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">☑</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(id)}</div><div class=\\\"ro\\\">Inactive · uncheck to remove</div></div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  tiles.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"alloc-tile all-tile ${allSel ? 'sel' : ''}\\\" onclick=\\\"toggleAllAlloc()\\\">\u001b[39m\n\u001b[31m      <span class=\\\"cb\\\">${allSel ? '✓' : '⌂'}</span>\u001b[39m\n\u001b[31m      <div><div class=\\\"nm\\\">All Employees</div><div class=\\\"ro\\\">${_allEmps.length} active · click to ${allSel ? 'deselect' : 'select'} all</div></div>\u001b[39m\n\u001b[31m    </div>` +\u001b[39m\n\u001b[31m    _allEmps.map(e => {\u001b[39m\n\u001b[31m      const sel = _allocSet.has(e.id);\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"alloc-tile ${sel ? 'sel' : ''}\\\" onclick=\\\"toggleAlloc('${esc(e.id)}')\\\">\u001b[39m\n\u001b[31m          <span class=\\\"cb\\\">${sel ? '☑' : '☐'}</span>\u001b[39m\n\u001b[31m          <div><div class=\\\"nm\\\">${esc(e.name)}</div><div class=\\\"ro\\\">${esc(e.role || '')}</div></div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + inactiveHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAlloc(empId) {\u001b[39m\n\u001b[31m  if (_allocSet.has(empId)) _allocSet.delete(empId); else _allocSet.add(empId);\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleAllAlloc() {\u001b[39m\n\u001b[31m  if (_allocSet.size === _allEmps.length) _allocSet = new Set();\u001b[39m\n\u001b[31m  else _allocSet = new Set(_allEmps.map(e => e.id));\u001b[39m\n\u001b[31m  renderAllocTiles();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveAlloc() {\u001b[39m\n\u001b[31m  const counterId = detailCounter.counter_id;\u001b[39m\n\u001b[31m  const jobId     = (document.getElementById('alloc-job_id').value || '').trim() || null;\u001b[39m\n\u001b[31m  // 1. Insert rows for newly-checked employees\u001b[39m\n\u001b[31m  const toAdd = [...(_allocSet)].filter(emp => !_existingAlloc.has(emp + '__' + (jobId || '')));\u001b[39m\n\u001b[31m  // 2. Delete rows for unchecked employees (only matching this job_id scope)\u001b[39m\n\u001b[31m  const toRemove = [...(_existingAlloc.entries())]\u001b[39m\n\u001b[31m    .filter(([key, _]) => {\u001b[39m\n\u001b[31m      const [emp, j] = key.split('__');\u001b[39m\n\u001b[31m      return j === (jobId || '') && !_allocSet.has(emp);\u001b[39m\n\u001b[31m    })\u001b[39m\n\u001b[31m    .map(([_, allocId]) => allocId);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (toAdd.length) {\u001b[39m\n\u001b[31m      const rows = toAdd.map(emp => ({\u001b[39m\n\u001b[31m        employee_id: emp, counter_id: counterId, job_id: jobId,\u001b[39m\n\u001b[31m        allocated_by: myId, source: _allocSet.size === _allEmps.length ? 'all_employees_tile' : 'supervisor_manual',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      await api('/allocation', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(rows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (toRemove.length) {\u001b[39m\n\u001b[31m      const idList = toRemove.join(',');\u001b[39m\n\u001b[31m      await api(`/allocation?allocation_id=in.(${idList})`, {\u001b[39m\n\u001b[31m        method: 'DELETE', headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    closeModal('modal-alloc');\u001b[39m\n\u001b[31m    toast(`Allocated to ${_allocSet.size} employee${_allocSet.size !== 1 ? 's' : ''} ✓`, 'ok');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Allocation failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 4.5 — CLIENTS ADMIN (manage brand-side users)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _clients         = [];                     // list from client_admin_v\u001b[39m\n\u001b[31mlet _clientFilter    = 'all';                  // current filter tile\u001b[39m\n\u001b[31mlet _clientUsersMap  = new Map();              // client_id → array of users\u001b[39m\n\u001b[31mlet _clientInvitesMap = new Map();             // client_id → array of pending invites\u001b[39m\n\u001b[31mlet _expandedClient  = null;                   // currently-expanded client_id\u001b[39m\n\u001b[31mlet _inviteClientId  = null;                   // for the invite modal·\u001b[39m\n\u001b[31mfunction setCFilter(e, val) {\u001b[39m\n\u001b[31m  _clientFilter = val;\u001b[39m\n\u001b[31m  document.querySelectorAll('#cli-tiles .tile').forEach(t =>\u001b[39m\n\u001b[31m    t.classList.toggle('sel', t.dataset.cfilter === val));\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openClients() {\u001b[39m\n\u001b[31m  showScreen('s-clients');\u001b[39m\n\u001b[31m  await loadClients();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClients() {\u001b[39m\n\u001b[31m  document.getElementById('clients-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _clients = await api('/client_admin_v?order=is_active.desc,name.asc');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _clients = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderClients();\u001b[39m\n\u001b[31m  refreshClientsBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshClientsBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_admin_v?is_active=eq.true&select=client_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('clients-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Clients (${arr.length})` : 'Clients';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.6 — Notifications ───────────────────────────────────\u001b[39m\n\u001b[31mlet _notify     = [];\u001b[39m\n\u001b[31mlet _notifyTab  = 'unseen';·\u001b[39m\n\u001b[31masync function openNotify() {\u001b[39m\n\u001b[31m  showScreen('s-notify');\u001b[39m\n\u001b[31m  await loadNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _notify = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed to load: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setNotifyTab(tab) {\u001b[39m\n\u001b[31m  _notifyTab = tab;\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-unseen').classList.toggle('on', tab === 'unseen');\u001b[39m\n\u001b[31m  document.getElementById('nf-tab-all').classList.toggle('on',    tab === 'all');\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _notifyMeta(evt) {\u001b[39m\n\u001b[31m  if (evt === 'recce_marked_official')    return { icon: '🏁', title: 'Recce marked official',  chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'client_response')          return { icon: '💬', title: 'Client response',        chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'magic_link_login')         return { icon: '🔐', title: 'Magic-link login',       chip: '' };\u001b[39m\n\u001b[31m  if (evt === 'suspicious_login')         return { icon: '⚠️', title: 'Suspicious login',       chip: 'evt-response' };\u001b[39m\n\u001b[31m  if (evt === 'supervisor_weekly_digest') return { icon: '📨', title: 'Weekly digest sent',     chip: 'evt-official' };\u001b[39m\n\u001b[31m  if (evt === 'recce_comment')            return { icon: '💬', title: 'Recce comment',          chip: 'evt-response' };\u001b[39m\n\u001b[31m  return { icon: '🔔', title: evt, chip: '' };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _fmtRel(ts) {\u001b[39m\n\u001b[31m  if (!ts) return '';\u001b[39m\n\u001b[31m  const d = new Date(ts);\u001b[39m\n\u001b[31m  const diff = (Date.now() - d.getTime()) / 1000;\u001b[39m\n\u001b[31m  if (diff < 60)   return Math.floor(diff) + 's ago';\u001b[39m\n\u001b[31m  if (diff < 3600) return Math.floor(diff/60) + 'm ago';\u001b[39m\n\u001b[31m  if (diff < 86400) return Math.floor(diff/3600) + 'h ago';\u001b[39m\n\u001b[31m  return Math.floor(diff/86400) + 'd ago';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderNotify() {\u001b[39m\n\u001b[31m  const out = document.getElementById('notify-out');\u001b[39m\n\u001b[31m  const list = _notify.filter(n => _notifyTab === 'all' ? true : n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📭</div><div>${_notifyTab === 'unseen' ? 'No unseen notifications.' : 'No notifications yet.'}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(n => {\u001b[39m\n\u001b[31m    const m   = _notifyMeta(n.event_type);\u001b[39m\n\u001b[31m    const p   = n.payload || {};\u001b[39m\n\u001b[31m    const seen = n.status === 'suppressed';\u001b[39m\n\u001b[31m    let body  = '';\u001b[39m\n\u001b[31m    let chips = '';\u001b[39m\n\u001b[31m    if (n.event_type === 'recce_marked_official') {\u001b[39m\n\u001b[31m      body  = `<b>${esc(p.picked_by || '?')}</b> picked Recce as official for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.counter_id || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'client_response') {\u001b[39m\n\u001b[31m      body  = `Client marked Recce <b>${esc(p.client_status || '?')}</b> for <b>${esc(p.brand || '?')}</b> at counter <b>${esc(p.counter_id || '?')}</b>.${p.client_comment ? `<br><span style=\\\"color:var(--muted)\\\">\\\"${esc(p.client_comment)}\\\"</span>` : ''}`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '?')}</span><span class=\\\"nf-chip\\\">${esc(p.client_status || '?')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'magic_link_login') {\u001b[39m\n\u001b[31m      body  = `Sign-in to Recce Portal from <b>${esc(p.ip || 'unknown IP')}</b>.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.client_user_id || '')}</span>`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'suspicious_login') {\u001b[39m\n\u001b[31m      const reasons = Array.isArray(p.reasons) ? p.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      body  = `Risk-flagged login for <b>${esc(p.email || p.client_user_id || '?')}</b> from <b>${esc(p.ip || 'unknown IP')}</b>${reasons ? ` · <span style=\\\"color:var(--muted)\\\">${esc(reasons)}</span>` : ''}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip ${p.risk === 'high' ? 'evt-response' : ''}\\\">${esc(p.risk || 'risk')}</span>${p.client_user_id ? `<span class=\\\"nf-chip\\\">${esc(p.client_user_id)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'recce_comment') {\u001b[39m\n\u001b[31m      const from = p.from === 'supervisor' ? '360 team' : 'Client user';\u001b[39m\n\u001b[31m      body  = `<b>${esc(from)}</b> on Recce <b>${esc(p.sub_id || '?')}</b>: <span style=\\\"color:var(--muted)\\\">\\\"${esc(p.preview || '')}\\\"</span>`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.brand || '')}</span>${p.author_name ? `<span class=\\\"nf-chip\\\">${esc(p.author_name)}</span>` : ''}`;\u001b[39m\n\u001b[31m    } else if (n.event_type === 'supervisor_weekly_digest') {\u001b[39m\n\u001b[31m      const w = p.window || {};\u001b[39m\n\u001b[31m      const li = p.logins || {};\u001b[39m\n\u001b[31m      const rp = p.response || {};\u001b[39m\n\u001b[31m      body  = `Sent weekly digest to <b>${esc(p.employee_id || '?')}</b> · logins ${esc(String(li.total || 0))} (high ${esc(String(li.high || 0))}), responses ${esc(String((rp.approved || 0) + (rp.approved_with_changes || 0) + (rp.rejected || 0)))}, top users ${esc(String(p.top_count || 0))}.`;\u001b[39m\n\u001b[31m      chips = `<span class=\\\"nf-chip\\\">${esc(p.employee_id || '')}</span><span class=\\\"nf-chip\\\">${esc(String(w.from || '').slice(0, 10))} → ${esc(String(w.to || '').slice(0, 10))}</span>`;\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      body  = `<pre style=\\\"white-space:pre-wrap;font-size:10.5px;color:var(--muted);margin:0\\\">${esc(JSON.stringify(p))}</pre>`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"notify-card${seen ? '' : ' unseen'}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"nf-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <div class=\\\"nf-title\\\">${esc(m.title)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"nf-when\\\">${esc(_fmtRel(n.created_at))}</div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-chips\\\">\u001b[39m\n\u001b[31m          <span class=\\\"nf-chip ${m.chip}\\\">${esc(n.event_type)}</span>\u001b[39m\n\u001b[31m          ${chips}\u001b[39m\n\u001b[31m          ${seen ? '<span class=\\\"nf-chip status-suppressed\\\">seen</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"nf-actions\\\">\u001b[39m\n\u001b[31m          ${seen ? '' : `<button class=\\\"seen\\\" onclick=\\\"markNotifySeen(${n.notify_id})\\\">Mark seen</button>`}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markNotifySeen(id) {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: id }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    const row = _notify.find(n => Number(n.notify_id) === Number(id));\u001b[39m\n\u001b[31m    if (row) row.status = 'suppressed';\u001b[39m\n\u001b[31m    renderNotify();\u001b[39m\n\u001b[31m    refreshNotifyBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Could not mark seen: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function markAllNotifySeen() {\u001b[39m\n\u001b[31m  const unseen = _notify.filter(n => n.status !== 'suppressed');\u001b[39m\n\u001b[31m  if (!unseen.length) { toast('Nothing to mark', 'ok'); return; }\u001b[39m\n\u001b[31m  if (!confirm(`Mark ${unseen.length} notification(s) as seen?`)) return;\u001b[39m\n\u001b[31m  for (const n of unseen) {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api('/rpc/admin_mark_notification_seen', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_notify_id: n.notify_id }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      n.status = 'suppressed';\u001b[39m\n\u001b[31m    } catch (_) {}\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderNotify();\u001b[39m\n\u001b[31m  refreshNotifyBadge();\u001b[39m\n\u001b[31m  toast(`Marked ${unseen.length} seen`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshNotifyBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const rows = await api('/rpc/admin_list_notifications', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_limit: 100 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const unseen = (rows || []).filter(n => n.status !== 'suppressed').length;\u001b[39m\n\u001b[31m    const el = document.getElementById('notify-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = unseen > 0 ? `Notify (${unseen})` : 'Notify';\u001b[39m\n\u001b[31m    const btn = document.getElementById('btn-notify');\u001b[39m\n\u001b[31m    if (btn) btn.style.borderColor = unseen > 0 ? 'var(--accent)' : '';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.7 — Audit log ──────────────────────────────────────\u001b[39m\n\u001b[31mlet _audit            = [];\u001b[39m\n\u001b[31mlet _auditDebounce    = null;·\u001b[39m\n\u001b[31masync function openAudit() {\u001b[39m\n\u001b[31m  showScreen('s-audit');\u001b[39m\n\u001b[31m  await loadAudit();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction loadAuditDebounced() {\u001b[39m\n\u001b[31m  clearTimeout(_auditDebounce);\u001b[39m\n\u001b[31m  _auditDebounce = setTimeout(loadAudit, 350);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  const action = (document.getElementById('audit-action').value || '').trim() || null;\u001b[39m\n\u001b[31m  const user   = (document.getElementById('audit-user').value   || '').trim() || null;\u001b[39m\n\u001b[31m  const fromD  = (document.getElementById('audit-from').value   || '').trim();\u001b[39m\n\u001b[31m  const toD    = (document.getElementById('audit-to').value     || '').trim();\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _audit = await api('/rpc/admin_list_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id:  myId,\u001b[39m\n\u001b[31m        p_limit:          fromD || toD ? 5000 : 200,\u001b[39m\n\u001b[31m        p_client_user_id: user,\u001b[39m\n\u001b[31m        p_action:         action,\u001b[39m\n\u001b[31m        p_from:           fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:             toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⚠️</div><div>Failed: ${esc(e.message)}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAudit();\u001b[39m\n\u001b[31m  refreshAuditBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.12 — CSV export of currently-loaded audit rows\u001b[39m\n\u001b[31mfunction exportAuditCsv() {\u001b[39m\n\u001b[31m  if (!_audit.length) { toast('Nothing to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = ['created_at','action','client_user_email','client_user_id','client_id','sub_id','ip','user_agent','detail'];\u001b[39m\n\u001b[31m  const esc = v => `\\\"${String(v == null ? '' : v).replace(/\\\"/g, '\\\"\\\"')}\\\"`;\u001b[39m\n\u001b[31m  const lines = [cols.join(',')];\u001b[39m\n\u001b[31m  for (const r of _audit) {\u001b[39m\n\u001b[31m    lines.push(cols.map(c => esc(c === 'detail' ? JSON.stringify(r.detail || {}) : r[c])).join(','));\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `audit_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  document.body.appendChild(a); a.click(); a.remove();\u001b[39m\n\u001b[31m  toast(`Exported ${_audit.length} rows`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pruneAuditNow() {\u001b[39m\n\u001b[31m  if (!confirm('Delete audit rows older than 180 days? Cannot be undone.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_prune_audit', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_days: 180 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    toast(`Pruned ${j.deleted_count} row(s)`, 'ok');\u001b[39m\n\u001b[31m    await loadAudit();\u001b[39m\n\u001b[31m  } catch (e) { toast('Prune failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.17 — supervisor comment thread ──────────────────\u001b[39m\n\u001b[31mlet _commentSubId = null;·\u001b[39m\n\u001b[31masync function openCommentsModal(subId, brand) {\u001b[39m\n\u001b[31m  _commentSubId = subId;\u001b[39m\n\u001b[31m  document.getElementById('comments-sub-label').textContent = `${subId.slice(0, 22)}${subId.length > 22 ? '…' : ''}${brand ? ` (${brand})` : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m  document.getElementById('modal-comments').classList.add('show');\u001b[39m\n\u001b[31m  await loadCommentsModal();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadCommentsModal() {\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  let rows = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    rows = await api('/rpc/list_comments_for_sub', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_session_id:    null,\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_sub_id:        _commentSubId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('comments-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const out = document.getElementById('comments-out');\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">💬</div><div>No messages yet. Start the conversation.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = rows.map(r => `\u001b[39m\n\u001b[31m    <div class=\\\"cmt-msg ${esc(r.author_type)}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ch\\\">\u001b[39m\n\u001b[31m        <span>${esc(r.author_type === 'supervisor' ? ('360 · ' + (r.author_name || r.author_id || 'team')) : (r.author_name || r.author_id || 'Client'))}</span>\u001b[39m\n\u001b[31m        <span>${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"cb\\\">${esc(r.body)}</div>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  `).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function postSupervisorComment() {\u001b[39m\n\u001b[31m  const body = (document.getElementById('comment-body').value || '').trim();\u001b[39m\n\u001b[31m  if (!body) { toast('Type a message first', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_commentSubId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('comment-post');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Posting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/add_comment_as_supervisor', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_sub_id: _commentSubId, p_body: body }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'failed');\u001b[39m\n\u001b[31m    document.getElementById('comment-body').value = '';\u001b[39m\n\u001b[31m    toast('Posted ✓', 'ok');\u001b[39m\n\u001b[31m    await loadCommentsModal();\u001b[39m\n\u001b[31m  } catch (e) { toast('Failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m  finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Post reply';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.14 — audit stats modal ──────────────────────────\u001b[39m\n\u001b[31masync function openAuditStats() {\u001b[39m\n\u001b[31m  const fromD = (document.getElementById('audit-from').value || '').trim();\u001b[39m\n\u001b[31m  const toD   = (document.getElementById('audit-to').value   || '').trim();\u001b[39m\n\u001b[31m  document.getElementById('stats-window').textContent =\u001b[39m\n\u001b[31m    fromD || toD ? `${fromD || '…'} → ${toD || '…'}` : 'last 7 days';\u001b[39m\n\u001b[31m  document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m    `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-stats').classList.add('show');\u001b[39m\n\u001b[31m  let j;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    j = await api('/rpc/admin_audit_stats', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({\u001b[39m\n\u001b[31m        p_supervisor_id: myId,\u001b[39m\n\u001b[31m        p_from: fromD ? new Date(fromD + 'T00:00:00').toISOString() : null,\u001b[39m\n\u001b[31m        p_to:   toD   ? new Date(toD   + 'T23:59:59').toISOString() : null,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!j?.ok) {\u001b[39m\n\u001b[31m    document.getElementById('stats-out').innerHTML =\u001b[39m\n\u001b[31m      `<div class=\\\"empty\\\">⚠️ ${esc(j?.error || 'failed')}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderAuditStats(j);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _bar(parts, total) {\u001b[39m\n\u001b[31m  if (!total) return '<div style=\\\"font-size:11px;color:var(--muted);margin-top:6px\\\">No data in window.</div>';\u001b[39m\n\u001b[31m  return `<div class=\\\"bar\\\">${\u001b[39m\n\u001b[31m    parts.map(([cls, n]) => `<div class=\\\"${cls}\\\" style=\\\"width:${(n / total * 100).toFixed(1)}%\\\"></div>`).join('')\u001b[39m\n\u001b[31m  }</div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAuditStats(j) {\u001b[39m\n\u001b[31m  const lr = j.logins_by_risk || { low: 0, medium: 0, high: 0, total: 0 };\u001b[39m\n\u001b[31m  const rm = j.response_mix    || { approved: 0, rejected: 0, approved_with_changes: 0, pending: 0, total: 0 };\u001b[39m\n\u001b[31m  const top = Array.isArray(j.top_users) ? j.top_users : [];\u001b[39m\n\u001b[31m  const delay = j.avg_response_delay_hours;\u001b[39m\n\u001b[31m  const delayTxt = delay == null ? '—' : `${Number(delay).toFixed(1)}h`;\u001b[39m\n\u001b[31m  const out = document.getElementById('stats-out');\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"stat-grid\\\">\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Logins by risk</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${lr.total || 0}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">low ${lr.low || 0} · med ${lr.medium || 0} · high ${lr.high || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([['low', lr.low || 0], ['medium', lr.medium || 0], ['high', lr.high || 0]], lr.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">low</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">medium</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">high</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card\\\">\u001b[39m\n\u001b[31m        <h4>Avg response delay</h4>\u001b[39m\n\u001b[31m        <div class=\\\"big\\\">${esc(delayTxt)}</div>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">Recce official → client response (window)</div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Response mix (all official Recces)</h4>\u001b[39m\n\u001b[31m        <div class=\\\"sub\\\">approved ${rm.approved || 0} · w/ changes ${rm.approved_with_changes || 0} · rejected ${rm.rejected || 0} · pending ${rm.pending || 0}</div>\u001b[39m\n\u001b[31m        ${_bar([\u001b[39m\n\u001b[31m          ['approved',              rm.approved || 0],\u001b[39m\n\u001b[31m          ['approved_with_changes', rm.approved_with_changes || 0],\u001b[39m\n\u001b[31m          ['rejected',              rm.rejected || 0],\u001b[39m\n\u001b[31m          ['pending',               rm.pending || 0],\u001b[39m\n\u001b[31m        ], rm.total || 0)}\u001b[39m\n\u001b[31m        <div class=\\\"legend\\\">\u001b[39m\n\u001b[31m          <span style=\\\"color:#22c55e\\\">approved</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#eab308\\\">w/ changes</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#ef4444\\\">rejected</span>\u001b[39m\n\u001b[31m          <span style=\\\"color:#6b7280\\\">pending</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"stat-card full\\\">\u001b[39m\n\u001b[31m        <h4>Top active users (window)</h4>\u001b[39m\n\u001b[31m        ${top.length === 0\u001b[39m\n\u001b[31m          ? '<div class=\\\"sub\\\">No activity in window.</div>'\u001b[39m\n\u001b[31m          : top.slice(0, 5).map(u => `\u001b[39m\n\u001b[31m              <div class=\\\"top-user\\\">\u001b[39m\n\u001b[31m                <div class=\\\"tu-email\\\">${esc(u.email || u.client_user_id)}</div>\u001b[39m\n\u001b[31m                <div class=\\\"tu-count\\\">👁 ${u.n_views || 0} · 💬 ${u.n_responses || 0}</div>\u001b[39m\n\u001b[31m              </div>`).join('')\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _auditMeta(action) {\u001b[39m\n\u001b[31m  if (action === 'login')           return { icon: '🔐', cls: 'login',    label: 'login'    };\u001b[39m\n\u001b[31m  if (action === 'recce_view')      return { icon: '👁',  cls: 'view',     label: 'view'     };\u001b[39m\n\u001b[31m  if (action === 'response_submit') return { icon: '💬', cls: 'response', label: 'response' };\u001b[39m\n\u001b[31m  return { icon: '•', cls: '', label: action };\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderAudit() {\u001b[39m\n\u001b[31m  const out = document.getElementById('audit-out');\u001b[39m\n\u001b[31m  const riskFilter = (document.getElementById('audit-risk')?.value || '').trim();\u001b[39m\n\u001b[31m  const filtered = _audit.filter(r => {\u001b[39m\n\u001b[31m    if (!riskFilter) return true;\u001b[39m\n\u001b[31m    const lvl = r.action === 'login' ? (r.detail?.risk || 'low') : null;\u001b[39m\n\u001b[31m    if (riskFilter === 'high')   return lvl === 'high';\u001b[39m\n\u001b[31m    if (riskFilter === 'medium') return lvl === 'medium' || lvl === 'high';\u001b[39m\n\u001b[31m    return true;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  if (!filtered.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📋</div><div>No audit rows match.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = filtered.map(r => {\u001b[39m\n\u001b[31m    const m = _auditMeta(r.action);\u001b[39m\n\u001b[31m    const d = r.detail || {};\u001b[39m\n\u001b[31m    let body = '';\u001b[39m\n\u001b[31m    let riskChip = '';\u001b[39m\n\u001b[31m    if (r.action === 'login') {\u001b[39m\n\u001b[31m      const risk = d.risk || 'low';\u001b[39m\n\u001b[31m      const reasons = Array.isArray(d.reasons) ? d.reasons.join(', ') : '';\u001b[39m\n\u001b[31m      riskChip = `<span class=\\\"au-risk ${esc(risk)}\\\">${esc(risk)}${reasons ? ' · ' + esc(reasons) : ''}</span>`;\u001b[39m\n\u001b[31m      body = `Magic-link login. Invite: <b>${esc(d.invite_id || '?')}</b>.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'recce_view') {\u001b[39m\n\u001b[31m      body = `Opened Recce <b>${esc(r.sub_id || '?')}</b> in viewer.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'response_submit') {\u001b[39m\n\u001b[31m      body = `Marked Recce <b>${esc(r.sub_id || '?')}</b> as <b>${esc(d.new_status || '?')}</b>${d.prev_status && d.prev_status !== d.new_status ? ` <span style=\\\"color:var(--muted)\\\">(was ${esc(d.prev_status)})</span>` : ''}${d.has_comment ? ' · 💬 with comment' : ''}${d.bulk_id ? ` · <span style=\\\"color:var(--muted)\\\">bulk ${esc(d.bulk_id.slice(0, 10))}…</span>` : ''}.`;\u001b[39m\n\u001b[31m    } else if (r.action === 'session_revoke') {\u001b[39m\n\u001b[31m      const by = d.by === 'supervisor' ? `supervisor ${esc(d.supervisor_id || '?')}` : 'self';\u001b[39m\n\u001b[31m      body = d.scope === 'all_others'\u001b[39m\n\u001b[31m        ? `Revoked ${esc(String(d.revoked_count || 0))} other session(s) (${by}).`\u001b[39m\n\u001b[31m        : `Revoked session ${esc((d.target_session_id || '').slice(0, 12))}… (${by}).`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"audit-row\\\">\u001b[39m\n\u001b[31m        <div class=\\\"au-top\\\">\u001b[39m\n\u001b[31m          <span class=\\\"au-icon\\\">${m.icon}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-action ${m.cls}\\\">${esc(m.label)}</span>\u001b[39m\n\u001b[31m          ${riskChip}\u001b[39m\n\u001b[31m          <span class=\\\"au-user\\\">${esc(r.client_user_email || r.client_user_id)}</span>\u001b[39m\n\u001b[31m          <span class=\\\"au-when\\\">${esc(_fmtRel(r.created_at))}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"au-body\\\">${body}</div>\u001b[39m\n\u001b[31m        <div class=\\\"au-meta\\\">\u001b[39m\n\u001b[31m          ${r.client_user_name ? `<span>${esc(r.client_user_name)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.client_id ? `<span>🏷 ${esc(r.client_id)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m          ${r.user_agent ? `<span title=\\\"${esc(r.user_agent)}\\\">🖥 ${esc(r.user_agent.slice(0, 32))}${r.user_agent.length > 32 ? '…' : ''}</span>` : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshAuditBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const since = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/client_audit_log?created_at=gte.${since}&select=audit_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('audit-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Audit (${arr.length})` : 'Audit';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClients() {\u001b[39m\n\u001b[31m  const active   = _clients.filter(c =>  c.is_active);\u001b[39m\n\u001b[31m  const inactive = _clients.filter(c => !c.is_active);\u001b[39m\n\u001b[31m  const withUsers = _clients.filter(c => c.active_user_count > 0);\u001b[39m\n\u001b[31m  const setTxt = (id, v) => { const el = document.getElementById(id); if (el) el.textContent = v; };\u001b[39m\n\u001b[31m  setTxt('cf-all',      _clients.length);\u001b[39m\n\u001b[31m  setTxt('cf-active',   active.length);\u001b[39m\n\u001b[31m  setTxt('cf-inactive', inactive.length);\u001b[39m\n\u001b[31m  setTxt('cf-users',    withUsers.length);\u001b[39m\n\u001b[31m  document.getElementById('clients-count').textContent =\u001b[39m\n\u001b[31m    `${active.length} active · ${_clients.length} total`;·\u001b[39m\n\u001b[31m  const q = (document.getElementById('cli-srch')?.value || '').toLowerCase().trim();\u001b[39m\n\u001b[31m  let list = _clientFilter === 'active'   ? active\u001b[39m\n\u001b[31m           : _clientFilter === 'inactive' ? inactive\u001b[39m\n\u001b[31m           : _clientFilter === 'users'    ? withUsers\u001b[39m\n\u001b[31m           : _clients;\u001b[39m\n\u001b[31m  if (q) list = list.filter(c =>\u001b[39m\n\u001b[31m    c.name.toLowerCase().includes(q) ||\u001b[39m\n\u001b[31m    (c.brand_codes || []).some(b => b.toLowerCase().includes(q)));·\u001b[39m\n\u001b[31m  const out = document.getElementById('clients-out');\u001b[39m\n\u001b[31m  if (!list.length) {\u001b[39m\n\u001b[31m    out.innerHTML = q || _clientFilter !== 'all'\u001b[39m\n\u001b[31m      ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No clients match. Try a different search or filter.</div></div>`\u001b[39m\n\u001b[31m      : `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🏷</div><div>No clients yet. Tap + Client to add one.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = list.map(c => {\u001b[39m\n\u001b[31m    const brandsTxt = (c.brand_codes || []).slice(0, 4).join(', ') + ((c.brand_codes || []).length > 4 ? '…' : '');\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"cli-card\\\" id=\\\"cli-card-${esc(c.client_id)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"cli-top\\\" onclick=\\\"toggleClient('${esc(c.client_id)}')\\\">\u001b[39m\n\u001b[31m          <div class=\\\"cli-name\\\">\u001b[39m\n\u001b[31m            <span class=\\\"name\\\">${esc(c.name)}${c.is_active ? '' : ' (inactive)'}</span>\u001b[39m\n\u001b[31m            <div class=\\\"meta\\\">\u001b[39m\n\u001b[31m              ${brandsTxt ? `<span class=\\\"cli-meta-chip\\\">${esc(brandsTxt)}</span>` : ''}\u001b[39m\n\u001b[31m              <span class=\\\"cli-meta-chip users\\\">${c.active_user_count} user${c.active_user_count !== 1 ? 's' : ''}</span>\u001b[39m\n\u001b[31m              ${c.pending_invite_count > 0 ? `<span class=\\\"cli-meta-chip pending\\\">${c.pending_invite_count} pending</span>` : ''}\u001b[39m\n\u001b[31m              ${c.revoked_user_count > 0 ? `<span class=\\\"cli-meta-chip inactive\\\">${c.revoked_user_count} revoked</span>` : ''}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"cli-body\\\" id=\\\"cli-body-${esc(c.client_id)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function toggleClient(clientId) {\u001b[39m\n\u001b[31m  const card = document.getElementById('cli-card-' + clientId);\u001b[39m\n\u001b[31m  if (!card) return;\u001b[39m\n\u001b[31m  const wasOpen = card.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) { _expandedClient = null; return; }\u001b[39m\n\u001b[31m  card.classList.add('expanded');\u001b[39m\n\u001b[31m  _expandedClient = clientId;\u001b[39m\n\u001b[31m  await loadClientDetail(clientId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadClientDetail(clientId) {\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:14px\\\"><span class=\\\"spin\\\">↻</span> Loading users + invites…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [users, invites] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_client_users', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: clientId }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    _clientUsersMap.set(clientId, users || []);\u001b[39m\n\u001b[31m    _clientInvitesMap.set(clientId, invites || []);\u001b[39m\n\u001b[31m    renderClientDetail(clientId);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = `<div class=\\\"empty\\\" style=\\\"padding:14px;color:var(--r)\\\">Load failed: ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderClientDetail(clientId) {\u001b[39m\n\u001b[31m  const users   = _clientUsersMap.get(clientId)   || [];\u001b[39m\n\u001b[31m  const invites = _clientInvitesMap.get(clientId) || [];\u001b[39m\n\u001b[31m  const body = document.getElementById('cli-body-' + clientId);\u001b[39m\n\u001b[31m  if (!body) return;\u001b[39m\n\u001b[31m  const usersHtml = users.length\u001b[39m\n\u001b[31m    ? users.map(u => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user ${u.is_active ? '' : 'revoked'}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"u-main\\\">\u001b[39m\n\u001b[31m            <div class=\\\"u-email\\\">${esc(u.email)}${u.display_name && u.display_name !== u.email ? ' · ' + esc(u.display_name) : ''}</div>\u001b[39m\n\u001b[31m            <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m              <span class=\\\"role\\\">${esc(u.role || 'Viewer')}</span>\u001b[39m\n\u001b[31m              <span class=\\\"scope\\\">${esc(u.scope_kind || 'all')}${u.scope_values && u.scope_values.length ? ' · ' + esc(u.scope_values.join(',').slice(0, 30)) : ''}</span>\u001b[39m\n\u001b[31m              ${u.last_login_at ? `<span>last login ${esc(fmtDate(u.last_login_at))}</span>` : '<span>never logged in</span>'}\u001b[39m\n\u001b[31m              ${u.is_active ? '' : '<span style=\\\"color:#fca5a5\\\">REVOKED</span>'}\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"openUserSessions('${esc(u.client_user_id)}', '${esc(u.email)}')\\\">🔑 Sessions</button>\u001b[39m\n\u001b[31m            ${u.is_active\u001b[39m\n\u001b[31m              ? `<button class=\\\"danger\\\" onclick=\\\"revokeClientUser('${esc(u.client_user_id)}', '${esc(clientId)}', '${esc(u.email)}')\\\">Revoke</button>`\u001b[39m\n\u001b[31m              : `<button onclick=\\\"reactivateClientUser('${esc(u.client_user_id)}', '${esc(clientId)}')\\\">Re-activate</button>`}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:14px\\\">No users yet.</div>`;·\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — ${esc(i.role)} · expires ${esc(fmtDate(i.expires_at))}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"revokeInvite('${esc(i.invite_id)}', '${esc(clientId)}', '${esc(i.email)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';·\u001b[39m\n\u001b[31m  body.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"cli-section-title\\\">Users (${users.length})</div>\u001b[39m\n\u001b[31m    ${usersHtml}\u001b[39m\n\u001b[31m    ${invites.length ? `<div class=\\\"cli-section-title\\\">Pending invites (${invites.length})</div>${invitesHtml}` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"cli-row-actions\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"closeClientDetail()\\\">Collapse</button>\u001b[39m\n\u001b[31m      <button class=\\\"invite\\\" onclick=\\\"openInvite('${esc(clientId)}')\\\">✉ Invite user</button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction closeClientDetail() {\u001b[39m\n\u001b[31m  document.querySelectorAll('.cli-card.expanded').forEach(c => c.classList.remove('expanded'));\u001b[39m\n\u001b[31m  _expandedClient = null;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── New Client ──────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openNewClient() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  ['ncf-name','ncf-brand_codes','ncf-contact_name','ncf-contact_email','ncf-notes']\u001b[39m\n\u001b[31m    .forEach(id => { const el = document.getElementById(id); if (el) el.value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('modal-newclient').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveNewClient() {\u001b[39m\n\u001b[31m  const name = (document.getElementById('ncf-name').value || '').trim();\u001b[39m\n\u001b[31m  if (!name) { toast('Name required', 'err'); return; }\u001b[39m\n\u001b[31m  const brand_codes = (document.getElementById('ncf-brand_codes').value || '')\u001b[39m\n\u001b[31m    .split(',').map(s => s.trim().toUpperCase()).filter(Boolean);\u001b[39m\n\u001b[31m  const clientId = 'cli-' + slugify(name) + '-' + Math.floor(Math.random() * 0xffff).toString(36);\u001b[39m\n\u001b[31m  const payload = {\u001b[39m\n\u001b[31m    client_id: clientId,\u001b[39m\n\u001b[31m    name,\u001b[39m\n\u001b[31m    brand_codes,\u001b[39m\n\u001b[31m    contact_name:  trim('ncf-contact_name'),\u001b[39m\n\u001b[31m    contact_email: trim('ncf-contact_email'),\u001b[39m\n\u001b[31m    notes:         trim('ncf-notes'),\u001b[39m\n\u001b[31m    created_by:    myId,\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/client', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      body:    JSON.stringify(payload),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    closeModal('modal-newclient');\u001b[39m\n\u001b[31m    await loadClients();\u001b[39m\n\u001b[31m    toast('Client created ✓', 'ok');\u001b[39m\n\u001b[31m  } catch (e) { toast('Create failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Invite User ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction openInvite(clientId) {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _inviteClientId = clientId;\u001b[39m\n\u001b[31m  ['invf-email','invf-display_name','invf-scope_values'].forEach(id => { document.getElementById(id).value = ''; });\u001b[39m\n\u001b[31m  document.getElementById('invf-role').value = 'Viewer';\u001b[39m\n\u001b[31m  document.getElementById('invf-scope_kind').value = 'all';\u001b[39m\n\u001b[31m  const c = _clients.find(x => x.client_id === clientId);\u001b[39m\n\u001b[31m  document.getElementById('invite-ttl').textContent = `✉ Invite user · ${c?.name || clientId}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-invite').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function saveInvite() {\u001b[39m\n\u001b[31m  const email = (document.getElementById('invf-email').value || '').trim();\u001b[39m\n\u001b[31m  if (!email || !/^[^\\\\s@]+@[^\\\\s@]+\\\\.[^\\\\s@]+$/.test(email)) {\u001b[39m\n\u001b[31m    toast('Please enter a valid email', 'err'); return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const scope_kind = document.getElementById('invf-scope_kind').value;\u001b[39m\n\u001b[31m  const raw_values = (document.getElementById('invf-scope_values').value || '').trim();\u001b[39m\n\u001b[31m  const scope_values = scope_kind === 'all' ? []\u001b[39m\n\u001b[31m    : raw_values.split(',').map(s => s.trim()).filter(Boolean);\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/invite_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        p_email:         email,\u001b[39m\n\u001b[31m        p_client_id:     _inviteClientId,\u001b[39m\n\u001b[31m        p_role:          document.getElementById('invf-role').value,\u001b[39m\n\u001b[31m        p_scope_kind:    scope_kind,\u001b[39m\n\u001b[31m        p_scope_values:  scope_values,\u001b[39m\n\u001b[31m        p_display_name:  trim('invf-display_name'),\u001b[39m\n\u001b[31m        p_invited_by:    myId,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    closeModal('modal-invite');\u001b[39m\n\u001b[31m    toast(`Invite queued — mail worker delivers within 30s`, 'ok');\u001b[39m\n\u001b[31m    if (_expandedClient === _inviteClientId) await loadClientDetail(_inviteClientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Invite failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Phase 4.8 — sessions modal (supervisor) ───────────────────\u001b[39m\n\u001b[31mlet _sessionsForUser = null;·\u001b[39m\n\u001b[31masync function openUserSessions(cuId, email) {\u001b[39m\n\u001b[31m  _sessionsForUser = cuId;\u001b[39m\n\u001b[31m  document.getElementById('sess-ttl').textContent = `🔑 Sessions — ${email}`;\u001b[39m\n\u001b[31m  document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">⏳</div><div>Loading…</div></div>`;\u001b[39m\n\u001b[31m  document.getElementById('modal-sessions').classList.add('show');\u001b[39m\n\u001b[31m  await loadUserSessions();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUserSessions() {\u001b[39m\n\u001b[31m  if (!_sessionsForUser) return;\u001b[39m\n\u001b[31m  let rows, invites = [];\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const [sRows, iRows] = await Promise.all([\u001b[39m\n\u001b[31m      api('/rpc/admin_list_sessions', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_user_id: _sessionsForUser, p_limit: 50 }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      api('/rpc/admin_list_invites', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        body:   JSON.stringify({ p_supervisor_id: myId, p_client_id: null, p_client_user_id: _sessionsForUser }),\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m    ]);\u001b[39m\n\u001b[31m    rows = sRows; invites = iRows || [];\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    document.getElementById('sess-modal-out').innerHTML = `<div class=\\\"empty\\\">⚠️ ${esc(e.message)}</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  const active = rows.filter(r => r.is_active).length;\u001b[39m\n\u001b[31m  const out = document.getElementById('sess-modal-out');\u001b[39m\n\u001b[31m  const invitesHtml = invites.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"cli-section-title\\\" style=\\\"margin-top:8px\\\">Pending invites (${invites.length})</div>` +\u001b[39m\n\u001b[31m      invites.map(i => `\u001b[39m\n\u001b[31m        <div class=\\\"cli-invite\\\">\u001b[39m\n\u001b[31m          <span>✉ ${esc(i.email)} — issued ${esc(_fmtRel(i.issued_at))} · expires ${esc(new Date(i.expires_at).toLocaleString('en-IN'))} · by ${esc(i.invited_by || '?')}</span>\u001b[39m\n\u001b[31m          <div class=\\\"actions\\\">\u001b[39m\n\u001b[31m            <button onclick=\\\"adminRevokePendingInvite('${esc(i.invite_id)}')\\\">Revoke</button>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`).join('')\u001b[39m\n\u001b[31m    : '';\u001b[39m\n\u001b[31m  if (!rows.length && !invites.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions or pending invites.</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!rows.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">🔑</div><div>No sessions yet.</div></div>${invitesHtml}`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">${active} active · ${rows.length} total (last 50)</div>\u001b[39m\n\u001b[31m    ` + rows.map(r => {\u001b[39m\n\u001b[31m      const status = r.is_active\u001b[39m\n\u001b[31m        ? '<span class=\\\"cli-meta-chip users\\\">ACTIVE</span>'\u001b[39m\n\u001b[31m        : (r.revoked_at\u001b[39m\n\u001b[31m            ? '<span class=\\\"cli-meta-chip inactive\\\">REVOKED</span>'\u001b[39m\n\u001b[31m            : '<span class=\\\"cli-meta-chip pending\\\">EXPIRED</span>');\u001b[39m\n\u001b[31m      return `\u001b[39m\n\u001b[31m        <div class=\\\"cli-user${r.is_active ? '' : ' revoked'}\\\" style=\\\"flex-direction:column;align-items:stretch\\\">\u001b[39m\n\u001b[31m          <div style=\\\"display:flex;gap:6px;align-items:center;margin-bottom:2px\\\">\u001b[39m\n\u001b[31m            ${status}\u001b[39m\n\u001b[31m            <span style=\\\"font-size:10.5px;color:var(--muted);margin-left:auto\\\">${esc(r.session_id.slice(0, 14))}…</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div style=\\\"font-size:11.5px;font-weight:700;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc((r.user_agent || 'Unknown device').slice(0, 70))}</div>\u001b[39m\n\u001b[31m          <div class=\\\"u-meta\\\">\u001b[39m\n\u001b[31m            ${r.ip ? `<span>📡 ${esc(r.ip)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>Issued ${esc(_fmtRel(r.issued_at))}</span>\u001b[39m\n\u001b[31m            <span>Last used ${esc(r.last_used_at ? _fmtRel(r.last_used_at) : '—')}</span>\u001b[39m\n\u001b[31m            <span>${r.revoked_at ? `Revoked ${esc(_fmtRel(r.revoked_at))}` : `Expires ${esc(new Date(r.expires_at).toLocaleDateString('en-IN'))}`}</span>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          ${r.is_active ? `\u001b[39m\n\u001b[31m            <div class=\\\"actions\\\" style=\\\"justify-content:flex-end;margin-top:6px\\\">\u001b[39m\n\u001b[31m              <button class=\\\"danger\\\" onclick=\\\"adminRevokeSession('${esc(r.session_id)}')\\\">Revoke session</button>\u001b[39m\n\u001b[31m            </div>` : ''}\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m    }).join('') + invitesHtml;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokePendingInvite(inviteId) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this pending invite? The link in the email will stop working.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function adminRevokeSession(sid) {\u001b[39m\n\u001b[31m  if (!confirm('Revoke this session? That device will be signed out.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_session', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_supervisor_id: myId, p_session_id: sid }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast('Session revoked', 'ok');\u001b[39m\n\u001b[31m    await loadUserSessions();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeClientUser(cuId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke access for ${email}? Any active sessions will be killed; they will not be able to log in.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error(j?.error || 'unknown');\u001b[39m\n\u001b[31m    toast(`Revoked ✓${j.sessions_revoked ? ` (${j.sessions_revoked} session${j.sessions_revoked !== 1 ? 's' : ''} killed)` : ''}`, 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function reactivateClientUser(cuId, clientId) {\u001b[39m\n\u001b[31m  if (!confirm('Re-activate this user? They will need to request a fresh login link.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/admin_reactivate_client_user', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_client_user_id: cuId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Re-activated', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Re-activate failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function revokeInvite(inviteId, clientId, email) {\u001b[39m\n\u001b[31m  if (!confirm(`Revoke the pending invite for ${email}? The magic link in their email will stop working.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const j = await api('/rpc/admin_revoke_invite', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({ p_supervisor_id: myId, p_invite_id: inviteId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!j?.ok) throw new Error('not_found_or_already_used');\u001b[39m\n\u001b[31m    toast('Invite revoked', 'ok');\u001b[39m\n\u001b[31m    await loadClientDetail(clientId);\u001b[39m\n\u001b[31m    refreshClientsBadge();\u001b[39m\n\u001b[31m  } catch (e) { toast('Revoke failed: ' + e.message, 'err'); }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 3 — MULTI-RECCE REVIEW (supervisor picks \\\"official\\\")\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _mrgGroups = [];\u001b[39m\n\u001b[31mlet _mrgRecces = new Map();   // group_key (counter_id||group_key) → recce rows·\u001b[39m\n\u001b[31masync function openMultiRecce() {\u001b[39m\n\u001b[31m  showScreen('s-multirecce');\u001b[39m\n\u001b[31m  await loadMultiRecce();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    _mrgGroups = await api('/multi_recce_groups_v?order=last_visit.desc&limit=300');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _mrgGroups = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderMultiRecce();\u001b[39m\n\u001b[31m  refreshMultiRecceBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshMultiRecceBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/multi_recce_groups_v?select=counter_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('multirecce-badge');\u001b[39m\n\u001b[31m    if (el) el.textContent = arr.length > 0 ? `Multi-Recce (${arr.length})` : 'Multi-Recce';\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderMultiRecce() {\u001b[39m\n\u001b[31m  document.getElementById('mrg-count').textContent = `${_mrgGroups.length} group${_mrgGroups.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m  const out = document.getElementById('mrg-out');\u001b[39m\n\u001b[31m  if (!_mrgGroups.length) {\u001b[39m\n\u001b[31m    out.innerHTML = '<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📌</div><div>No multi-Recce groups</div><div style=\\\"font-size:11px;margin-top:4px\\\">A group forms when 2+ Recces are submitted against the same counter + job_id.</div></div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _mrgGroups.map(g => {\u001b[39m\n\u001b[31m    const hasOfficial = g.official_count > 0;\u001b[39m\n\u001b[31m    const groupId = g.counter_id + '||' + g.group_key;\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m      <div class=\\\"mrg-row\\\" id=\\\"mrg-${cssEsc(groupId)}\\\">\u001b[39m\n\u001b[31m        <div class=\\\"mrg-top\\\" onclick=\\\"toggleMrg('${esc(groupId)}')\\\">\u001b[39m\n\u001b[31m          <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m            <div class=\\\"mrg-name\\\">${esc(g.display_name)}</div>\u001b[39m\n\u001b[31m            <div class=\\\"mrg-meta\\\">\u001b[39m\n\u001b[31m              ${g.city ? `<span>📍 ${esc(g.city)}</span>` : ''}\u001b[39m\n\u001b[31m              <span>Group: ${esc(g.group_key)}</span>\u001b[39m\n\u001b[31m              <span>${g.recce_count} Recces</span>\u001b[39m\n\u001b[31m              <span>Last ${esc(fmtDate(g.last_visit))}</span>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <button onclick=\\\"event.stopPropagation();openDetail('${esc(g.counter_id)}')\\\" style=\\\"font-size:11px;padding:3px 9px;border-radius:12px;border:1px solid var(--accent);background:transparent;color:var(--accent);cursor:pointer;white-space:nowrap;flex-shrink:0\\\">View Counter →</button>\u001b[39m\n\u001b[31m          <span class=\\\"mrg-pick ${hasOfficial ? 'has' : 'none'}\\\">${hasOfficial ? '✓ Picked' : 'Pick now'}</span>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div class=\\\"mrg-body\\\" id=\\\"mrg-body-${cssEsc(groupId)}\\\"></div>\u001b[39m\n\u001b[31m      </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction cssEsc(s) { return (s || '').replace(/[^a-z0-9_-]/gi, '_').slice(0, 80); }·\u001b[39m\n\u001b[31masync function toggleMrg(groupId) {\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const row  = document.getElementById('mrg-' + safe);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  document.querySelectorAll('.mrg-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (wasOpen) return;\u001b[39m\n\u001b[31m  row.classList.add('expanded');\u001b[39m\n\u001b[31m  await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadMrgRecces(groupId) {\u001b[39m\n\u001b[31m  const [counterId, groupKey] = groupId.split('||');\u001b[39m\n\u001b[31m  const safe = cssEsc(groupId);\u001b[39m\n\u001b[31m  const body = document.getElementById('mrg-body-' + safe);\u001b[39m\n\u001b[31m  body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\"><span class=\\\"spin\\\">↻</span> Loading Recces…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Filter by counter_id AND matching group key (job_id or free_text_identifier)\u001b[39m\n\u001b[31m    let q = `/submissions?counter_id=eq.${encodeURIComponent(counterId)}&order=visit_date.desc,submitted_at.desc`;\u001b[39m\n\u001b[31m    if (groupKey === '<no-id>') {\u001b[39m\n\u001b[31m      q += '&job_id=is.null&free_text_identifier=is.null';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // group_key matches either job_id or free_text_identifier\u001b[39m\n\u001b[31m      q += `&or=(job_id.eq.${encodeURIComponent(groupKey)},free_text_identifier.eq.${encodeURIComponent(groupKey)})`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const recces = await fetch(`${DB}${q}`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'recce', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    }).then(r => r.ok ? r.json() : []);\u001b[39m\n\u001b[31m    _mrgRecces.set(groupId, recces);\u001b[39m\n\u001b[31m    renderMrgBody(groupId, body, recces);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px;color:var(--r)\\\">Load failed: ' + esc(e.message) + '</div>';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// Phase 4.0a: sign a /recce-view-proxy/ token for the current Hub session,\u001b[39m\n\u001b[31m// then open the returned URL. Replaces the previous direct window.open on\u001b[39m\n\u001b[31m// view_url / pdf_url which is now 401-blocked by Traefik.\u001b[39m\n\u001b[31masync function openSignedRecceView(subId, kind = 'html') {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const hubAuth = 'Hub ' + btoa(JSON.stringify({\u001b[39m\n\u001b[31m      empId: session.empId, name: session.name, role: session.role || 'user',\u001b[39m\n\u001b[31m      loginAt: session.loginAt || new Date().toISOString(),\u001b[39m\n\u001b[31m    }));\u001b[39m\n\u001b[31m    const r = await fetch('/recce-view-proxy/sign', {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': hubAuth },\u001b[39m\n\u001b[31m      body:    JSON.stringify({ sub_id: subId, ttl_s: 900 }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!r.ok) throw new Error(r.status + ' ' + (await r.text()).slice(0, 80));\u001b[39m\n\u001b[31m    const j = await r.json();\u001b[39m\n\u001b[31m    const url = kind === 'pdf' ? j.urls.pdf : j.urls.html;\u001b[39m\n\u001b[31m    window.open(url, '_blank', 'noopener');\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('View failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function pickOfficial(subId, groupId) {\u001b[39m\n\u001b[31m  if (!confirm('Mark this Recce as the official one for the client?\\\\nAny prior official in this group will be un-marked.')) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/mark_official_recce', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_sub_id: subId, p_picked_by: myId }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Marked official ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Pick failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PAST RECCES TAB — filterable history browser (ADR-027 chips)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderRecceTab() {\u001b[39m\n\u001b[31m  const tab = document.getElementById('d-recces');\u001b[39m\n\u001b[31m  if (!tab) return;·\u001b[39m\n\u001b[31m  const brands    = [...new Set(detailRecces.map(r => r.brand).filter(Boolean))].sort();\u001b[39m\n\u001b[31m  const campaigns = [...new Set(detailRecces.map(r => r.job_id || r.free_text_identifier).filter(Boolean))].sort();·\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): `campaigns` mixes real job_id FKs with free-text\u001b[39m\n\u001b[31m  // identifiers (r.free_text_identifier is already human text, never a raw ID —\u001b[39m\n\u001b[31m  // only r.job_id values need resolving). Render synchronously with whatever is\u001b[39m\n\u001b[31m  // cached now (raw id as last-resort fallback), then batch-resolve any missing\u001b[39m\n\u001b[31m  // job_ids and re-render once — never a per-row fetch.\u001b[39m\n\u001b[31m  const realJobIds = [...new Set(detailRecces.map(r => r.job_id).filter(Boolean))];\u001b[39m\n\u001b[31m  const missingJobNames = realJobIds.filter(id => !_jobNameCache[id]);\u001b[39m\n\u001b[31m  if (missingJobNames.length) _ensureJobNames(missingJobNames).then(changed => { if (changed) renderRecceTab(); });·\u001b[39m\n\u001b[31m  let list = detailRecces;\u001b[39m\n\u001b[31m  if (_recceFilter.brands.size)    list = list.filter(r => _recceFilter.brands.has(r.brand));\u001b[39m\n\u001b[31m  if (_recceFilter.campaigns.size) list = list.filter(r => _recceFilter.campaigns.has(r.job_id || r.free_text_identifier));\u001b[39m\n\u001b[31m  if (_recceFilter.from)           list = list.filter(r => r.visit_date >= _recceFilter.from);\u001b[39m\n\u001b[31m  if (_recceFilter.to)             list = list.filter(r => r.visit_date <= _recceFilter.to);·\u001b[39m\n\u001b[31m  const chipRow = (dim, vals, active, labelFn) =>\u001b[39m\n\u001b[31m    `<div class=\\\"recce-filter-chips\\\">${\u001b[39m\n\u001b[31m      vals.map(v => `<span class=\\\"filter-chip${active.has(v) ? ' active' : ''}\\\" onclick=\\\"rcChip('${dim}','${esc(v)}')\\\">${esc(labelFn ? labelFn(v) : v)}</span>`).join('')\u001b[39m\n\u001b[31m    }${active.size ? `<span class=\\\"filter-chip\\\" style=\\\"color:var(--r);border-color:var(--r)\\\" onclick=\\\"rcClear('${dim}')\\\">✕ Clear</span>` : ''}</div>`;·\u001b[39m\n\u001b[31m  const showFilter = brands.length > 1 || campaigns.length > 1 || detailRecces.length > 5;\u001b[39m\n\u001b[31m  const filterBar = showFilter ? `<div class=\\\"recce-filter-bar\\\">\u001b[39m\n\u001b[31m    ${brands.length > 1    ? chipRow('brands', brands, _recceFilter.brands) : ''}\u001b[39m\n\u001b[31m    ${campaigns.length > 1 ? chipRow('campaigns', campaigns, _recceFilter.campaigns, v => _jobNameCache[v] || v) : ''}\u001b[39m\n\u001b[31m    ${detailRecces.length > 5 ? `<div class=\\\"recce-date-row\\\">\u001b[39m\n\u001b[31m      <label>From</label><input type=\\\"date\\\" value=\\\"${_recceFilter.from}\\\" onchange=\\\"_recceFilter.from=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m      <label>To</label><input type=\\\"date\\\" value=\\\"${_recceFilter.to}\\\" onchange=\\\"_recceFilter.to=this.value;renderRecceTab()\\\">\u001b[39m\n\u001b[31m    </div>` : ''}\u001b[39m\n\u001b[31m  </div>` : '';·\u001b[39m\n\u001b[31m  const cards = list.length\u001b[39m\n\u001b[31m    ? list.map(r => {\u001b[39m\n\u001b[31m        const photos = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m        return `\u001b[39m\n\u001b[31m        <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m          <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m            <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand || '—')}</span>\u001b[39m\n\u001b[31m            ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m            ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(r.submitted_by || '—')}</span>\u001b[39m\n\u001b[31m            ${r.job_id ? `<span>Campaign: ${esc(_jobNameCache[r.job_id] || r.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            <span>${photos} 📷</span>\u001b[39m\n\u001b[31m            ${r.has_branding ? `<span>🏷 ${r.branding_count||0} branding</span>` : ''}\u001b[39m\n\u001b[31m            ${r.has_video ? '<span>🎥 video</span>' : ''}\u001b[39m\n\u001b[31m            ${r.gps_lat ? `<a href=\\\"https://maps.google.com/?q=${r.gps_lat},${r.gps_lng}\\\" target=\\\"_blank\\\" style=\\\"color:var(--accent);font-size:11px;text-decoration:none\\\">📍 Map</a>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m          <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m            ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m            ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>`;\u001b[39m\n\u001b[31m      }).join('')\u001b[39m\n\u001b[31m    : `<div class=\\\"empty\\\" style=\\\"padding:20px 0\\\"><div class=\\\"empty-icon\\\">🔍</div><div>No recces match filters</div></div>`;·\u001b[39m\n\u001b[31m  tab.innerHTML = filterBar + (list.length === 0 && !detailRecces.length\u001b[39m\n\u001b[31m    ? `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📸</div><div>No recces recorded for this counter yet</div></div>`\u001b[39m\n\u001b[31m    : cards);·\u001b[39m\n\u001b[31m  const recTab = document.querySelector('.tabs button[data-tab=\\\"recces\\\"]');\u001b[39m\n\u001b[31m  if (recTab) {\u001b[39m\n\u001b[31m    const n = detailRecces.length;\u001b[39m\n\u001b[31m    const f = list.length;\u001b[39m\n\u001b[31m    recTab.textContent = n === 0 ? 'Past Recces'\u001b[39m\n\u001b[31m      : f < n ? `Past Recces (${f}/${n})`\u001b[39m\n\u001b[31m      : `Past Recces (${n})`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcChip(dim, val) {\u001b[39m\n\u001b[31m  if (_recceFilter[dim].has(val)) _recceFilter[dim].delete(val);\u001b[39m\n\u001b[31m  else _recceFilter[dim].add(val);\u001b[39m\n\u001b[31m  renderRecceTab();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction rcClear(dim) { _recceFilter[dim].clear(); renderRecceTab(); }·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// MULTI-RECCE — Conflict Resolver\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════·\u001b[39m\n\u001b[31mfunction renderMrgBody(groupId, body, recces) {\u001b[39m\n\u001b[31m  if (!recces.length) {\u001b[39m\n\u001b[31m    body.innerHTML = '<div class=\\\"empty\\\" style=\\\"padding:16px\\\">No recces found</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (recces.length === 2) {\u001b[39m\n\u001b[31m    body.innerHTML = _recceConflictUI(recces[0], recces[1], groupId);\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // 3+ recces — list with individual pick buttons\u001b[39m\n\u001b[31m    body.innerHTML = `<div style=\\\"font-size:11px;color:var(--muted);margin-bottom:8px\\\">\u001b[39m\n\u001b[31m      ⚠ ${recces.length} recces — pick one as official, or open the counter's Past Recces tab for full history.\u001b[39m\n\u001b[31m    </div>${recces.map(r => _recceSingleCard(r, groupId)).join('')}`;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceSingleCard(r, groupId) {\u001b[39m\n\u001b[31m  const total = (r.front_photos||0)+(r.inside_photos||0)+(r.addl_photos||0);\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"recce-card ${r.is_official_for_client ? 'official' : ''}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"rc-top\\\">\u001b[39m\n\u001b[31m        <span class=\\\"rc-date\\\">${esc(fmtDate(r.visit_date))} · ${esc(r.brand||'—')}</span>\u001b[39m\n\u001b[31m        <div style=\\\"display:flex;gap:5px;align-items:center\\\">\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '<span class=\\\"rc-official-badge\\\">✓ Official</span>' : ''}\u001b[39m\n\u001b[31m          ${r.superseded_by_sub_id ? '<span class=\\\"rc-official-badge\\\" style=\\\"background:#1e3a5f;color:#93c5fd\\\">↳ Superseded</span>' : ''}\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-meta\\\">\u001b[39m\n\u001b[31m        <span>By ${esc(r.submitted_by||'—')}</span>\u001b[39m\n\u001b[31m        ${r.submitted_at ? `<span>Submitted ${esc(fmtDate(r.submitted_at.slice(0,10)))}</span>` : ''}\u001b[39m\n\u001b[31m        <span>${total} 📷</span>\u001b[39m\n\u001b[31m        ${r.has_branding ? `<span>🏷 ${r.branding_count||0}</span>` : ''}\u001b[39m\n\u001b[31m        ${r.has_video ? '<span>🎥</span>' : ''}\u001b[39m\n\u001b[31m        ${r.official_picked_by ? `<span style=\\\"color:#86efac\\\">Picked by ${esc(r.official_picked_by)}</span>` : ''}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"rc-actions\\\">\u001b[39m\n\u001b[31m        ${r.view_url ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','html')\\\">👁 View</button>` : ''}\u001b[39m\n\u001b[31m        ${r.pdf_url  ? `<button class=\\\"rc-view\\\" onclick=\\\"openSignedRecceView('${esc(r.sub_id)}','pdf')\\\">📄 PDF</button>` : ''}\u001b[39m\n\u001b[31m        ${r.is_official_for_client ? `<button class=\\\"rc-view\\\" onclick=\\\"openCommentsModal('${esc(r.sub_id)}','${esc(r.brand||'')}')\\\">💬</button>` : ''}\u001b[39m\n\u001b[31m        <button class=\\\"rc-pick\\\" ${r.is_official_for_client ? 'disabled' : `onclick=\\\"pickOfficial('${esc(r.sub_id)}','${esc(groupId)}')\\\"`}>\u001b[39m\n\u001b[31m          ${r.is_official_for_client ? '✓ Official' : '★ Pick'}\u001b[39m\n\u001b[31m        </button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceConflictUI(rA, rB, groupId) {\u001b[39m\n\u001b[31m  const sg = cssEsc(groupId);\u001b[39m\n\u001b[31m  const officialA = rA.is_official_for_client, officialB = rB.is_official_for_client;\u001b[39m\n\u001b[31m  return `\u001b[39m\n\u001b[31m    <div class=\\\"mrg-ab-tabs\\\">\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab active\\\" id=\\\"tab-a-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','a')\\\">\u001b[39m\n\u001b[31m        Recce A · ${esc(fmtDate(rA.visit_date))}${officialA ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button class=\\\"mrg-ab-tab\\\" id=\\\"tab-b-${sg}\\\" onclick=\\\"mrgShowAB('${sg}','b')\\\">\u001b[39m\n\u001b[31m        Recce B · ${esc(fmtDate(rB.visit_date))}${officialB ? ' ✓' : ''}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div id=\\\"card-a-${sg}\\\">${_recceSingleCard(rA, groupId)}</div>\u001b[39m\n\u001b[31m    <div id=\\\"card-b-${sg}\\\" style=\\\"display:none\\\">${_recceSingleCard(rB, groupId)}</div>\u001b[39m\n\u001b[31m    <button onclick=\\\"mrgToggleDiff('${sg}')\\\" style=\\\"width:100%;margin:6px 0;padding:6px;background:var(--card);border:1px solid var(--border);border-radius:var(--rads);color:var(--muted);font-size:11.5px;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m      ⇆ Compare A vs B (field diff)\u001b[39m\n\u001b[31m    </button>\u001b[39m\n\u001b[31m    <div id=\\\"diff-${sg}\\\" style=\\\"display:none\\\">${_recceDiffTable(rA, rB)}</div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-resolve-bar\\\">\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rA.sub_id)}','${esc(groupId)}')\\\" ${officialA ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialA ? '✓ A is Official' : '★ A is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"pickOfficial('${esc(rB.sub_id)}','${esc(groupId)}')\\\" ${officialB ? 'disabled' : ''} style=\\\"background:var(--card);color:var(--text)\\\">\u001b[39m\n\u001b[31m        ${officialB ? '✓ B is Official' : '★ B is Official'}\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m      <button onclick=\\\"mrgToggleOverride('${sg}')\\\" style=\\\"background:rgba(251,191,36,.12);color:#fde68a;border-color:rgba(251,191,36,.3)\\\">\u001b[39m\n\u001b[31m        ⊕ Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    <div class=\\\"mrg-override-panel\\\" id=\\\"ovr-${sg}\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11.5px;color:var(--muted);margin-bottom:8px\\\">Choose base record and add a supervisor note. The other will be marked superseded.</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:14px;margin-bottom:8px;font-size:12.5px\\\">\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"a\\\" checked> Recce A as base</label>\u001b[39m\n\u001b[31m        <label><input type=\\\"radio\\\" name=\\\"ovr-base-${sg}\\\" value=\\\"b\\\"> Recce B as base</label>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <textarea id=\\\"ovr-note-${sg}\\\" placeholder=\\\"Supervisor note: reason for decision, what differs, which photos to use…\\\" rows=\\\"3\\\"\u001b[39m\n\u001b[31m        style=\\\"width:100%;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text);font-size:12px;padding:8px;resize:vertical;box-sizing:border-box;font-family:inherit\\\"></textarea>\u001b[39m\n\u001b[31m      <button onclick=\\\"applyOverride('${esc(rA.sub_id)}','${esc(rB.sub_id)}','${sg}','${esc(groupId)}')\\\"\u001b[39m\n\u001b[31m        style=\\\"margin-top:8px;width:100%;padding:10px;background:var(--accent);color:#fff;border:none;border-radius:var(--rads);font-size:13px;font-weight:700;cursor:pointer;font-family:inherit\\\">\u001b[39m\n\u001b[31m        Apply Supervisor Override\u001b[39m\n\u001b[31m      </button>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction _recceDiffTable(rA, rB) {\u001b[39m\n\u001b[31m  const FIELDS = [\u001b[39m\n\u001b[31m    { key: 'store_name',     label: 'Store name' },\u001b[39m\n\u001b[31m    { key: 'brand',          label: 'Brand' },\u001b[39m\n\u001b[31m    { key: 'visit_date',     label: 'Visit date', fmt: v => fmtDate(v) },\u001b[39m\n\u001b[31m    { key: 'submitted_by',   label: 'Agent' },\u001b[39m\n\u001b[31m    { key: 'address',        label: 'Address' },\u001b[39m\n\u001b[31m    { key: 'city',           label: 'City' },\u001b[39m\n\u001b[31m    { key: 'mobile',         label: 'Mobile' },\u001b[39m\n\u001b[31m    { key: 'has_branding',   label: 'Has branding', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'branding_count', label: 'Branding count', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'has_video',      label: 'Has video', fmt: v => v ? 'Yes' : 'No' },\u001b[39m\n\u001b[31m    { key: 'front_photos',   label: 'Front photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'inside_photos',  label: 'Inside photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'addl_photos',    label: 'Addl photos', fmt: v => String(v||0) },\u001b[39m\n\u001b[31m    { key: 'gps_lat',        label: 'GPS', fmt: (v, r) => r.gps_lat ? `${Number(r.gps_lat).toFixed(5)}, ${Number(r.gps_lng).toFixed(5)}` : '—' },\u001b[39m\n\u001b[31m    { key: 'status',         label: 'Status' },\u001b[39m\n\u001b[31m    { key: 'client_name',    label: 'Client name' },\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const fmt = (f, r) => f.fmt ? f.fmt(r[f.key], r) : (r[f.key] != null ? String(r[f.key]) : '—');\u001b[39m\n\u001b[31m  const rows = FIELDS.map(f => {\u001b[39m\n\u001b[31m    const vA = fmt(f, rA), vB = fmt(f, rB);\u001b[39m\n\u001b[31m    return `<tr class=\\\"${vA !== vB ? 'diff-row' : ''}\\\">\u001b[39m\n\u001b[31m      <td>${f.label}</td><td>${esc(vA)}</td><td>${esc(vB)}</td>\u001b[39m\n\u001b[31m    </tr>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  return `<table class=\\\"diff-table\\\">\u001b[39m\n\u001b[31m    <thead><tr><th>Field</th><th>Recce A</th><th>Recce B</th></tr></thead>\u001b[39m\n\u001b[31m    <tbody>${rows}</tbody>\u001b[39m\n\u001b[31m  </table>`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgShowAB(sg, which) {\u001b[39m\n\u001b[31m  document.getElementById('card-a-' + sg).style.display = which === 'a' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('card-b-' + sg).style.display = which === 'b' ? '' : 'none';\u001b[39m\n\u001b[31m  document.getElementById('tab-a-'  + sg).classList.toggle('active', which === 'a');\u001b[39m\n\u001b[31m  document.getElementById('tab-b-'  + sg).classList.toggle('active', which === 'b');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleDiff(sg) {\u001b[39m\n\u001b[31m  const el = document.getElementById('diff-' + sg);\u001b[39m\n\u001b[31m  if (el) el.style.display = el.style.display === 'none' ? '' : 'none';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction mrgToggleOverride(sg) {\u001b[39m\n\u001b[31m  document.getElementById('ovr-' + sg)?.classList.toggle('open');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyOverride(subIdA, subIdB, sg, groupId) {\u001b[39m\n\u001b[31m  const base = document.querySelector(`input[name=\\\"ovr-base-${sg}\\\"]:checked`)?.value || 'a';\u001b[39m\n\u001b[31m  const note = document.getElementById('ovr-note-' + sg)?.value.trim();\u001b[39m\n\u001b[31m  if (!note) { toast('Add a supervisor note before applying', 'err'); return; }\u001b[39m\n\u001b[31m  const winnerId = base === 'a' ? subIdA : subIdB;\u001b[39m\n\u001b[31m  const loserId  = base === 'a' ? subIdB : subIdA;\u001b[39m\n\u001b[31m  if (!confirm(`Mark Recce ${base.toUpperCase()} as official? The other will be marked superseded.`)) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api('/rpc/supervisor_pick_official', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body:   JSON.stringify({ p_winner_sub_id: winnerId, p_loser_sub_id: loserId, p_picked_by: myId, p_note: note }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Supervisor override applied ✓', 'ok');\u001b[39m\n\u001b[31m    await loadMrgRecces(groupId);\u001b[39m\n\u001b[31m    await loadMultiRecce();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Override failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31m// PHASE 2 — UPLOAD QUEUE (Strict Gate approval)\u001b[39m\n\u001b[31m// ══════════════════════════════════════════════════════════\u001b[39m\n\u001b[31mlet _uploads = [];\u001b[39m\n\u001b[31mlet _pendingUploadRows = [];   // parsed CSV rows awaiting submission·\u001b[39m\n\u001b[31m// ── Template Download ──────────────────────────────────────\u001b[39m\n\u001b[31mfunction exportCounterCSV() {\u001b[39m\n\u001b[31m  const list = filterCounters();\u001b[39m\n\u001b[31m  if (!list.length) { toast('No counters to export', 'err'); return; }\u001b[39m\n\u001b[31m  const cols = [\u001b[39m\n\u001b[31m    'counter_id','display_name','legal_name','proprietor_name',\u001b[39m\n\u001b[31m    'address_line1','address_line2','city','state','pincode',\u001b[39m\n\u001b[31m    'gstin','pan','fssai','drug_lic','counter_type','channel',\u001b[39m\n\u001b[31m    'customer_group','territory','distributor_name','notes','brand_codes',\u001b[39m\n\u001b[31m    'recce_count','last_recce_date','status','approval_status',\u001b[39m\n\u001b[31m    'primary_contact_name','primary_contact_phone',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const csvEscVal = v => {\u001b[39m\n\u001b[31m    if (v == null) return '';\u001b[39m\n\u001b[31m    const s = Array.isArray(v) ? v.join(',') : String(v);\u001b[39m\n\u001b[31m    return s.includes(',') || s.includes('\\\"') || s.includes('\\\\n')\u001b[39m\n\u001b[31m      ? '\\\"' + s.replace(/\\\"/g, '\\\"\\\"') + '\\\"'\u001b[39m\n\u001b[31m      : s;\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  const rows = [cols.join(',')];\u001b[39m\n\u001b[31m  list.forEach(c => rows.push(cols.map(k => csvEscVal(c[k])).join(',')));\u001b[39m\n\u001b[31m  const blob = new Blob([rows.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counters_export_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m  toast(`Exported ${list.length} counters`, 'ok');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction downloadUploadTemplate() {\u001b[39m\n\u001b[31m  // Collect distinct brand codes from loaded counters as a reference\u001b[39m\n\u001b[31m  const knownBrands = [...new Set([...(window.MASTER_BRAND_CODES || []),\u001b[39m\n\u001b[31m                                   ...allCounters.flatMap(c => c.brand_codes || [])])].sort().join(', ') || 'HP, HP_INK, SAMSUNG';\u001b[39m\n\u001b[31m  const lines = [\u001b[39m\n\u001b[31m    '# 360LM Counter Upload Template v2 | Fields marked * are MANDATORY | Multiple brand_codes: separate with comma e.g. HP,SAMSUNG',\u001b[39m\n\u001b[31m    `# Valid brand_codes in system: ${knownBrands}`,\u001b[39m\n\u001b[31m    `# counter_type values: sub_dealer / dealer / distributor / modern_trade / standalone / other`,\u001b[39m\n\u001b[31m    `# channel values: general_trade / modern_trade / institution / e_commerce / other`,\u001b[39m\n\u001b[31m    'display_name*,legal_name,proprietor_name,address_line1,address_line2,city*,state*,pincode*,gstin,pan,fssai,drug_lic,counter_type,channel,customer_group,territory,distributor_name,notes,brand_codes,primary_contact_name,primary_contact_phone',\u001b[39m\n\u001b[31m    'Example Store Name,,Proprietor Name,123 Main Street,,Mumbai,Maharashtra,400001,27AABCS1234A1ZS,,,,,dealer,general_trade,West Zone,City Zone,HP Distributor Ltd,First visit,HP,Ramesh Kumar,9876543210',\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  const blob = new Blob([lines.join('\\\\n')], { type: 'text/csv' });\u001b[39m\n\u001b[31m  const a = document.createElement('a');\u001b[39m\n\u001b[31m  a.href = URL.createObjectURL(blob);\u001b[39m\n\u001b[31m  a.download = `counter_upload_template_${new Date().toISOString().slice(0, 10)}.csv`;\u001b[39m\n\u001b[31m  a.click();\u001b[39m\n\u001b[31m  URL.revokeObjectURL(a.href);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── CSV Parse ─────────────────────────────────────────────\u001b[39m\n\u001b[31mfunction parseCSVLine(line) {\u001b[39m\n\u001b[31m  const result = [];\u001b[39m\n\u001b[31m  let cur = '', inQuote = false;\u001b[39m\n\u001b[31m  for (let i = 0; i < line.length; i++) {\u001b[39m\n\u001b[31m    const ch = line[i];\u001b[39m\n\u001b[31m    if (ch === '\\\"') {\u001b[39m\n\u001b[31m      if (inQuote && line[i + 1] === '\\\"') { cur += '\\\"'; i++; }\u001b[39m\n\u001b[31m      else inQuote = !inQuote;\u001b[39m\n\u001b[31m    } else if (ch === ',' && !inQuote) {\u001b[39m\n\u001b[31m      result.push(cur.trim()); cur = '';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      cur += ch;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  result.push(cur.trim());\u001b[39m\n\u001b[31m  return result;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction parseCSV(text) {\u001b[39m\n\u001b[31m  const lines = text.split(/\\\\r?\\\\n/);\u001b[39m\n\u001b[31m  const dataLines = lines.filter(l => l.trim() && !l.startsWith('#'));\u001b[39m\n\u001b[31m  if (dataLines.length < 2) return [];\u001b[39m\n\u001b[31m  const headers = parseCSVLine(dataLines[0]).map(h => h.replace(/\\\\*/g, '').toLowerCase().trim());\u001b[39m\n\u001b[31m  return dataLines.slice(1).map(l => {\u001b[39m\n\u001b[31m    const vals = parseCSVLine(l);\u001b[39m\n\u001b[31m    const obj = {};\u001b[39m\n\u001b[31m    headers.forEach((h, i) => { if (h) obj[h] = (vals[i] || '').trim(); });\u001b[39m\n\u001b[31m    return obj;\u001b[39m\n\u001b[31m  }).filter(r => Object.values(r).some(v => v));\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Handle file selection ─────────────────────────────────\u001b[39m\n\u001b[31mfunction handleUploadFileSelect(input) {\u001b[39m\n\u001b[31m  const file = input.files[0];\u001b[39m\n\u001b[31m  if (!file) return;\u001b[39m\n\u001b[31m  const reader = new FileReader();\u001b[39m\n\u001b[31m  reader.onload = e => {\u001b[39m\n\u001b[31m    const rows = parseCSV(e.target.result);\u001b[39m\n\u001b[31m    if (!rows.length) { toast('No data rows found in CSV', 'err'); return; }\u001b[39m\n\u001b[31m    _pendingUploadRows = rows;\u001b[39m\n\u001b[31m    // Pre-fill list name from filename\u001b[39m\n\u001b[31m    const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m    if (!nameEl.value) nameEl.value = file.name.replace(/\\\\.[^.]+$/, '').replace(/[_-]/g, ' ');\u001b[39m\n\u001b[31m    // Show preview\u001b[39m\n\u001b[31m    const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m    const sample = rows.slice(0, 3).map(r => r.display_name || r.name || JSON.stringify(r).slice(0, 60)).join(', ');\u001b[39m\n\u001b[31m    preview.innerHTML = `<b>${rows.length} rows</b> parsed from <b>${esc(file.name)}</b><br>Preview: ${esc(sample)}${rows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m  };\u001b[39m\n\u001b[31m  reader.onerror = () => toast('Failed to read file', 'err');\u001b[39m\n\u001b[31m  reader.readAsText(file);\u001b[39m\n\u001b[31m  input.value = ''; // reset so same file can be re-selected\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── AI Import ─────────────────────────────────────────────\u001b[39m\n\u001b[31mconst AI_CONVERT_URL    = '/counter-ai/';\u001b[39m\n\u001b[31mconst AI_CONVERT_SECRET = '6PItF6boihpxT1z7PDJClAnzR6QbfqPsQttIjfV7zPU';·\u001b[39m\n\u001b[31mlet _aiConvertedRows = [];·\u001b[39m\n\u001b[31mfunction openAiImport() {\u001b[39m\n\u001b[31m  if (!isSupervisor()) return;\u001b[39m\n\u001b[31m  _aiConvertedRows = [];\u001b[39m\n\u001b[31m  document.getElementById('ai-raw-input').value = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-detect-hint').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('ai-import-preview').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-use-rows').style.display = 'none';\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-ai-convert').textContent = 'Convert with AI';\u001b[39m\n\u001b[31m  document.getElementById('modal-ai-import').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction aiDetectStores(val) {\u001b[39m\n\u001b[31m  const hint = document.getElementById('ai-detect-hint');\u001b[39m\n\u001b[31m  if (!val.trim()) { hint.textContent = ''; return; }\u001b[39m\n\u001b[31m  const blocks = val.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim());\u001b[39m\n\u001b[31m  const n = blocks.length;\u001b[39m\n\u001b[31m  const batches = Math.ceil(n / 15);\u001b[39m\n\u001b[31m  hint.textContent = `~${n} store${n !== 1 ? 's' : ''} detected` +\u001b[39m\n\u001b[31m    (batches > 1 ? ` · ${batches} batches` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function runAiConvert(allowApi = false) {\u001b[39m\n\u001b[31m  const raw = document.getElementById('ai-raw-input').value.trim();\u001b[39m\n\u001b[31m  if (!raw) { toast('Paste a store list first', 'err'); return; }·\u001b[39m\n\u001b[31m  const btn      = document.getElementById('btn-ai-convert');\u001b[39m\n\u001b[31m  const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m  const previewEl = document.getElementById('ai-import-preview');\u001b[39m\n\u001b[31m  const useBtn   = document.getElementById('btn-ai-use-rows');·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Converting…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  const estBatches = Math.ceil(raw.trim().split(/\\\\n\\\\s*\\\\n/).filter(b => b.trim()).length / 15);\u001b[39m\n\u001b[31m  statusEl.textContent = allowApi\u001b[39m\n\u001b[31m    ? '⏳ Retrying via Anthropic API…'\u001b[39m\n\u001b[31m    : estBatches > 1\u001b[39m\n\u001b[31m      ? `⏳ Converting ${estBatches} batches… may take ${estBatches * 15}–${estBatches * 25}s`\u001b[39m\n\u001b[31m      : '⏳ Sending to AI… this may take 10–30 seconds';\u001b[39m\n\u001b[31m  previewEl.style.display = 'none';\u001b[39m\n\u001b[31m  useBtn.style.display = 'none';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL, {\u001b[39m\n\u001b[31m      method:  'POST',\u001b[39m\n\u001b[31m      headers: {\u001b[39m\n\u001b[31m        'Content-Type':  'application/json',\u001b[39m\n\u001b[31m        'Authorization': 'Bearer ' + AI_CONVERT_SECRET,\u001b[39m\n\u001b[31m      },\u001b[39m\n\u001b[31m      body: JSON.stringify({ raw, allow_api: allowApi }),\u001b[39m\n\u001b[31m    });·\u001b[39m\n\u001b[31m    const data = await resp.json();·\u001b[39m\n\u001b[31m    // Auto-routes failed — ask supervisor whether to use the API key\u001b[39m\n\u001b[31m    if (resp.status === 503 && data.need_api_permission) {\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.innerHTML =\u001b[39m\n\u001b[31m        '⚠ Auto AI routes (OAuth &amp; OpenRouter) unavailable. ' +\u001b[39m\n\u001b[31m        '<button class=\\\"btn btn-s\\\" style=\\\"font-size:11px;padding:3px 10px;margin-left:6px\\\" ' +\u001b[39m\n\u001b[31m        'onclick=\\\"confirmApiUsage()\\\">Allow Anthropic API</button>';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    if (!resp.ok || data.error) {\u001b[39m\n\u001b[31m      throw new Error(data.error || data.message || `HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    _aiConvertedRows = data.rows || [];\u001b[39m\n\u001b[31m    if (!_aiConvertedRows.length) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--or)';\u001b[39m\n\u001b[31m      statusEl.textContent = '⚠ AI returned no rows — try rephrasing or check the input.';\u001b[39m\n\u001b[31m      btn.disabled = false;\u001b[39m\n\u001b[31m      btn.textContent = 'Retry';\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }·\u001b[39m\n\u001b[31m    // Show preview table\u001b[39m\n\u001b[31m    statusEl.style.color = '#4ade80';\u001b[39m\n\u001b[31m    const batchInfo = data.batches > 1 ? ` (${data.batches} batches)` : '';\u001b[39m\n\u001b[31m    statusEl.textContent = `✓ ${_aiConvertedRows.length} store${_aiConvertedRows.length !== 1 ? 's' : ''} converted via ${data.mode || 'AI'}${batchInfo}.`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-count').textContent =\u001b[39m\n\u001b[31m      `${_aiConvertedRows.length} row${_aiConvertedRows.length !== 1 ? 's' : ''}`;\u001b[39m\n\u001b[31m    document.getElementById('ai-preview-table').innerHTML = _aiConvertedRows.map((r, i) =>\u001b[39m\n\u001b[31m      `<div style=\\\"padding:4px 0;${i ? 'border-top:1px solid var(--border)' : ''}\\\">` +\u001b[39m\n\u001b[31m        `<b>${esc(r.display_name || '(no name)')}</b>` +\u001b[39m\n\u001b[31m        (r.city    ? ` · ${esc(r.city)}`    : '') +\u001b[39m\n\u001b[31m        (r.state   ? `, ${esc(r.state)}`   : '') +\u001b[39m\n\u001b[31m        (r.pincode ? ` ${esc(r.pincode)}`  : '') +\u001b[39m\n\u001b[31m        (r.primary_contact_phone ? ` · 📞 ${esc(r.primary_contact_phone)}` : '') +\u001b[39m\n\u001b[31m        (r.counter_type ? ` <span style=\\\"color:var(--muted)\\\">[${esc(r.counter_type)}]</span>` : '') +\u001b[39m\n\u001b[31m      '</div>'\u001b[39m\n\u001b[31m    ).join('');\u001b[39m\n\u001b[31m    previewEl.style.display = '';\u001b[39m\n\u001b[31m    useBtn.style.display = '';\u001b[39m\n\u001b[31m    btn.textContent = 'Re-convert';\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m  } catch (err) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = `✗ ${err.message}`;\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Retry';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction confirmApiUsage() {\u001b[39m\n\u001b[31m  const ok = confirm(\u001b[39m\n\u001b[31m    'Auto AI routes (OAuth + OpenRouter) are currently unavailable.\\\\n\\\\n' +\u001b[39m\n\u001b[31m    'Allow use of Anthropic API key as fallback?\\\\n' +\u001b[39m\n\u001b[31m    '(This uses paid API credits from the configured API key.)'\u001b[39m\n\u001b[31m  );\u001b[39m\n\u001b[31m  if (!ok) {\u001b[39m\n\u001b[31m    const statusEl = document.getElementById('ai-import-status');\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '✗ API key usage declined. Try again later when OAuth or OpenRouter is available.';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  runAiConvert(true);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Campaign Enrichment — 4-phase guided flow (ADR-062) ──────────────────────·\u001b[39m\n\u001b[31m// Fuzzy name matching (inline Levenshtein, zero dependency)\u001b[39m\n\u001b[31mfunction _normName(s) {\u001b[39m\n\u001b[31m  return (s||'').toLowerCase().replace(/[^a-z0-9 ]/g,' ').replace(/\\\\s+/g,' ').trim();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _lev(a, b) {\u001b[39m\n\u001b[31m  const m = a.length, n = b.length;\u001b[39m\n\u001b[31m  const dp = Array.from({length:m+1}, (_,i) => Array.from({length:n+1}, (_,j) => i||j));\u001b[39m\n\u001b[31m  for (let i=1;i<=m;i++) for (let j=1;j<=n;j++)\u001b[39m\n\u001b[31m    dp[i][j] = a[i-1]===b[j-1] ? dp[i-1][j-1] : 1+Math.min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1]);\u001b[39m\n\u001b[31m  return dp[m][n];\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31mfunction _fuzzyScore(a, b) {\u001b[39m\n\u001b[31m  const na=_normName(a), nb=_normName(b);\u001b[39m\n\u001b[31m  const ml=Math.max(na.length,nb.length);\u001b[39m\n\u001b[31m  return ml ? 1-_lev(na,nb)/ml : 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mlet _enrichRows = [];  // [{aiRow, matchType, matchedId, matchScore, geo, decision}]\u001b[39m\n\u001b[31mlet _enrichIdx  = 0;·\u001b[39m\n\u001b[31mfunction _enrichShow(phase) {\u001b[39m\n\u001b[31m  ['enrich-p12','enrich-p3','enrich-p4'].forEach(id => {\u001b[39m\n\u001b[31m    const el = document.getElementById(id);\u001b[39m\n\u001b[31m    el.style.display = id===phase ? (phase==='enrich-p3'?'flex':'flex') : 'none';\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function startEnrichment() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  document.getElementById('ai-import-ttl').textContent = '🤖 Match & Enrich';\u001b[39m\n\u001b[31m  _enrichShow('enrich-p3');\u001b[39m\n\u001b[31m  _enrichRows = [];\u001b[39m\n\u001b[31m  _enrichIdx  = 0;·\u001b[39m\n\u001b[31m  // ── Stage A: 3-tier name matching ────────────────────────\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Matching ${_aiConvertedRows.length} stores to existing counters…`;\u001b[39m\n\u001b[31m  for (const aiRow of _aiConvertedRows) {\u001b[39m\n\u001b[31m    const name   = (aiRow.display_name||'').trim();\u001b[39m\n\u001b[31m    const gstin  = (aiRow.gstin||'').trim().toUpperCase();\u001b[39m\n\u001b[31m    const pin    = (aiRow.pincode||'').trim();\u001b[39m\n\u001b[31m    const city   = (aiRow.city||'').trim().toLowerCase();\u001b[39m\n\u001b[31m    let matchType='new', matchedId=null, matchScore=0;·\u001b[39m\n\u001b[31m    // Tier 1: GSTIN exact\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.gstin||'').toUpperCase()===gstin);\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 2: Name + pincode exact (case-insensitive)\u001b[39m\n\u001b[31m    if (!matchedId && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => (c.pincode||'')=== pin && _normName(c.display_name)===_normName(name));\u001b[39m\n\u001b[31m      if (m) { matchType='exact'; matchedId=m.counter_id; matchScore=1; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Tier 3: Fuzzy name (city-scoped, threshold 0.80)\u001b[39m\n\u001b[31m    if (!matchedId && name) {\u001b[39m\n\u001b[31m      let best=0, bestId=null;\u001b[39m\n\u001b[31m      for (const c of allCounters) {\u001b[39m\n\u001b[31m        if (city && c.city && c.city.toLowerCase()!==city) continue;\u001b[39m\n\u001b[31m        const s = _fuzzyScore(name, c.display_name||'');\u001b[39m\n\u001b[31m        if (s>best) { best=s; bestId=c.counter_id; }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      if (best>=0.80) { matchType='fuzzy'; matchedId=bestId; matchScore=best; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    _enrichRows.push({ aiRow, matchType, matchedId, matchScore, geo:null, decision:null });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Stage B: geo enrichment via /geo ─────────────────────\u001b[39m\n\u001b[31m  const geoTargets = _enrichRows.filter(r => r.aiRow.city);\u001b[39m\n\u001b[31m  document.getElementById('enrich-p3-status').textContent = `Enriching geo fields for ${geoTargets.length} stores via AI…`;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method:'POST',\u001b[39m\n\u001b[31m      headers:{'Content-Type':'application/json','Authorization':'Bearer '+AI_CONVERT_SECRET},\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: geoTargets.map((r,i) => ({\u001b[39m\n\u001b[31m        counter_id: String(i),\u001b[39m\n\u001b[31m        display_name: r.aiRow.display_name||'',\u001b[39m\n\u001b[31m        city: r.aiRow.city||'',\u001b[39m\n\u001b[31m        state: r.aiRow.state||'',\u001b[39m\n\u001b[31m      }))})\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (resp.ok) {\u001b[39m\n\u001b[31m      const data = await resp.json();\u001b[39m\n\u001b[31m      (data.suggestions||[]).forEach(sug => {\u001b[39m\n\u001b[31m        const idx = parseInt(sug.counter_id,10);\u001b[39m\n\u001b[31m        if (!isNaN(idx) && geoTargets[idx]) geoTargets[idx].geo = sug;\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch(e) { console.warn('[enrich] geo call failed:', e); }·\u001b[39m\n\u001b[31m  // ── Transition to Phase 4 ─────────────────────────────────\u001b[39m\n\u001b[31m  _enrichShow('enrich-p4');\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichPips() {\u001b[39m\n\u001b[31m  document.getElementById('enrich-pips').innerHTML =\u001b[39m\n\u001b[31m    _enrichRows.map((r,i) => `<div class=\\\"epip${i<_enrichIdx?' done':i===_enrichIdx?' active':''}\\\"></div>`).join('');\u001b[39m\n\u001b[31m  const done = _enrichRows.filter(r=>r.decision).length;\u001b[39m\n\u001b[31m  document.getElementById('enrich-pip-label').textContent =\u001b[39m\n\u001b[31m    `${done} of ${_enrichRows.length} reviewed${done===_enrichRows.length?' — ready to apply':''}`;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderEnrichCard(idx) {\u001b[39m\n\u001b[31m  if (idx >= _enrichRows.length) {\u001b[39m\n\u001b[31m    // All reviewed\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-accept').style.display = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-skip').style.display   = 'none';\u001b[39m\n\u001b[31m    document.getElementById('btn-enrich-apply').style.display  = '';\u001b[39m\n\u001b[31m    const accepted = _enrichRows.filter(r=>r.decision==='accept').length;\u001b[39m\n\u001b[31m    document.getElementById('enrich-apply-status').textContent =\u001b[39m\n\u001b[31m      `${accepted} accepted · ${_enrichRows.filter(r=>r.decision==='skip').length} skipped`;\u001b[39m\n\u001b[31m    renderEnrichPips();\u001b[39m\n\u001b[31m    document.getElementById('enrich-card').innerHTML =\u001b[39m\n\u001b[31m      `<div style=\\\"text-align:center;padding:20px;color:var(--g);font-size:13px\\\">✓ All ${_enrichRows.length} stores reviewed — click Apply to write changes</div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderEnrichPips();\u001b[39m\n\u001b[31m  const row  = _enrichRows[idx];\u001b[39m\n\u001b[31m  const ai   = row.aiRow;\u001b[39m\n\u001b[31m  const c    = row.matchedId ? allCounters.find(x=>x.counter_id===row.matchedId) : null;\u001b[39m\n\u001b[31m  const geo  = row.geo;\u001b[39m\n\u001b[31m  const pct  = Math.round(row.matchScore*100);·\u001b[39m\n\u001b[31m  const badgeCls = row.matchType==='exact' ? 'exact' : row.matchType==='fuzzy' ? 'fuzzy' : 'newc';\u001b[39m\n\u001b[31m  const badgeTxt = row.matchType==='exact' ? '✓ Exact Match'\u001b[39m\n\u001b[31m                 : row.matchType==='fuzzy' ? `≈ Fuzzy Match ${pct}%`\u001b[39m\n\u001b[31m                 : '+ New Counter';·\u001b[39m\n\u001b[31m  function diffRow(label, oldVal, newVal) {\u001b[39m\n\u001b[31m    if (!newVal || newVal===oldVal) return '';\u001b[39m\n\u001b[31m    return `<div class=\\\"enrich-diff-row\\\">\u001b[39m\n\u001b[31m      <span class=\\\"ef-label\\\">${label}</span>\u001b[39m\n\u001b[31m      ${oldVal ? `<span class=\\\"ef-old\\\">${esc(oldVal)}</span><span style=\\\"color:var(--muted)\\\">→</span>` : ''}\u001b[39m\n\u001b[31m      <span class=\\\"ef-new\\\">${esc(newVal)}</span>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  let diffs = '';\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    diffs += diffRow('State Code',  c.state_code||'',  geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',   c.city_tier||'',   geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Geo Cluster', c.geo_cluster||'', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',     c.channel||'',     ai.channel||'');\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    // New counter — show what will be created\u001b[39m\n\u001b[31m    diffs += diffRow('City',       '', ai.city||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State',      '', ai.state||'');\u001b[39m\n\u001b[31m    diffs += diffRow('State Code', '', geo?.state_code||'');\u001b[39m\n\u001b[31m    diffs += diffRow('City Tier',  '', geo?.city_tier||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Cluster',    '', geo?.geo_cluster||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Type',       '', ai.counter_type||'');\u001b[39m\n\u001b[31m    diffs += diffRow('Channel',    '', ai.channel||'');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (!diffs) diffs = `<div style=\\\"color:var(--muted);font-size:11px;padding:6px 0\\\">No enrichable fields found — accepting will make no DB changes</div>`;·\u001b[39m\n\u001b[31m  document.getElementById('enrich-card').innerHTML = `\u001b[39m\n\u001b[31m    <div style=\\\"display:flex;align-items:center;gap:8px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-weight:700;font-size:13px;flex:1\\\">${esc(ai.display_name||'Unnamed')}</div>\u001b[39m\n\u001b[31m      <span class=\\\"mbadge ${badgeCls}\\\">${badgeTxt}</span>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m    ${c ? `<div style=\\\"font-size:10px;color:var(--muted);margin-bottom:8px\\\">→ ${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</div>` : ''}\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:6px;font-weight:700;text-transform:uppercase;letter-spacing:.4px\\\">${c?'Suggested enrichments':'Will create'}</div>\u001b[39m\n\u001b[31m    ${diffs}\u001b[39m\n\u001b[31m  `;·\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-accept').style.display = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-skip').style.display   = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-enrich-apply').style.display  = 'none';\u001b[39m\n\u001b[31m  document.getElementById('enrich-apply-status').textContent = '';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction enrichDecide(decision) {\u001b[39m\n\u001b[31m  if (_enrichIdx >= _enrichRows.length) return;\u001b[39m\n\u001b[31m  _enrichRows[_enrichIdx].decision = decision;\u001b[39m\n\u001b[31m  _enrichIdx++;\u001b[39m\n\u001b[31m  renderEnrichCard(_enrichIdx);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyEnrichment() {\u001b[39m\n\u001b[31m  const btn   = document.getElementById('btn-enrich-apply');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('enrich-apply-status');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok=0, fail=0;\u001b[39m\n\u001b[31m  const newRows = [];·\u001b[39m\n\u001b[31m  for (const row of _enrichRows.filter(r=>r.decision==='accept')) {\u001b[39m\n\u001b[31m    if (row.matchType==='new') {\u001b[39m\n\u001b[31m      newRows.push(row.aiRow);\u001b[39m\n\u001b[31m      continue;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Enrich existing matched counter\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    const c   = allCounters.find(x=>x.counter_id===row.matchedId);\u001b[39m\n\u001b[31m    const geo = row.geo;\u001b[39m\n\u001b[31m    if (geo?.state_code  && geo.state_code  !== (c?.state_code||''))  patch.state_code  = geo.state_code;\u001b[39m\n\u001b[31m    if (geo?.city_tier   && geo.city_tier   !== (c?.city_tier||''))   patch.city_tier   = geo.city_tier;\u001b[39m\n\u001b[31m    if (geo?.geo_cluster && geo.geo_cluster !== (c?.geo_cluster||'')) patch.geo_cluster = geo.geo_cluster;\u001b[39m\n\u001b[31m    if (row.aiRow.channel && !c?.channel) patch.channel = row.aiRow.channel;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) { ok++; continue; }\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(row.matchedId)}`,\u001b[39m\n\u001b[31m        {method:'PATCH', headers:{Prefer:'return=minimal'}, body:JSON.stringify(patch)});\u001b[39m\n\u001b[31m      if (c) Object.assign(c, patch);\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // New counters → push to upload queue\u001b[39m\n\u001b[31m  if (newRows.length) {\u001b[39m\n\u001b[31m    _aiConvertedRows = newRows;\u001b[39m\n\u001b[31m    useAiRows();   // stages into _pendingUploadRows and opens upload modal\u001b[39m\n\u001b[31m  } else {\u001b[39m\n\u001b[31m    closeModal('modal-ai-import');\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  const msg = `Enrichment done — ${ok} counter${ok!==1?'s':''} updated${fail?`, ${fail} failed`:''}${newRows.length?`, ${newRows.length} new → upload queue`:''}`;\u001b[39m\n\u001b[31m  toast(msg, ok||newRows.length ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction useAiRows() {\u001b[39m\n\u001b[31m  if (!_aiConvertedRows.length) return;\u001b[39m\n\u001b[31m  // Normalise keys to match what the upload parser expects\u001b[39m\n\u001b[31m  _pendingUploadRows = _aiConvertedRows.map(r => ({\u001b[39m\n\u001b[31m    display_name:           r.display_name           || '',\u001b[39m\n\u001b[31m    legal_name:             r.legal_name             || '',\u001b[39m\n\u001b[31m    proprietor_name:        r.proprietor_name        || '',\u001b[39m\n\u001b[31m    address_line1:          r.address_line1          || '',\u001b[39m\n\u001b[31m    address_line2:          r.address_line2          || '',\u001b[39m\n\u001b[31m    city:                   r.city                   || '',\u001b[39m\n\u001b[31m    state:                  r.state                  || '',\u001b[39m\n\u001b[31m    pincode:                r.pincode                || '',\u001b[39m\n\u001b[31m    gstin:                  r.gstin                  || '',\u001b[39m\n\u001b[31m    pan:                    r.pan                    || '',\u001b[39m\n\u001b[31m    fssai:                  r.fssai                  || '',\u001b[39m\n\u001b[31m    drug_lic:               r.drug_lic               || '',\u001b[39m\n\u001b[31m    counter_type:           r.counter_type           || '',\u001b[39m\n\u001b[31m    channel:                r.channel                || '',\u001b[39m\n\u001b[31m    customer_group:         r.customer_group         || '',\u001b[39m\n\u001b[31m    territory:              r.territory              || '',\u001b[39m\n\u001b[31m    distributor_name:       r.distributor_name       || '',\u001b[39m\n\u001b[31m    notes:                  r.notes                  || '',\u001b[39m\n\u001b[31m    brand_codes:            Array.isArray(r.brand_codes) ? r.brand_codes.join(',') : (r.brand_codes || ''),\u001b[39m\n\u001b[31m    primary_contact_name:   r.primary_contact_name   || '',\u001b[39m\n\u001b[31m    primary_contact_phone:  r.primary_contact_phone  || '',\u001b[39m\n\u001b[31m  }));·\u001b[39m\n\u001b[31m  closeModal('modal-ai-import');·\u001b[39m\n\u001b[31m  const nameEl = document.getElementById('csvup-name');\u001b[39m\n\u001b[31m  if (!nameEl.value) nameEl.value = `AI Import ${new Date().toLocaleDateString('en-IN')}`;·\u001b[39m\n\u001b[31m  const preview = document.getElementById('csvup-preview');\u001b[39m\n\u001b[31m  const sample  = _pendingUploadRows.slice(0, 3).map(r => r.display_name || 'Unnamed').join(', ');\u001b[39m\n\u001b[31m  preview.innerHTML = `<b>${_pendingUploadRows.length} rows</b> from AI conversion<br>Preview: ${esc(sample)}${_pendingUploadRows.length > 3 ? '…' : ''}`;\u001b[39m\n\u001b[31m  document.getElementById('modal-csvup').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Submit upload to DB ───────────────────────────────────\u001b[39m\n\u001b[31masync function submitUploadRows() {\u001b[39m\n\u001b[31m  const listName = document.getElementById('csvup-name').value.trim();\u001b[39m\n\u001b[31m  if (!listName) { toast('Enter a list name', 'err'); return; }\u001b[39m\n\u001b[31m  if (!_pendingUploadRows.length) { toast('No rows to submit', 'err'); return; }\u001b[39m\n\u001b[31m  const btn = document.getElementById('csvup-submit');\u001b[39m\n\u001b[31m  btn.disabled = true; btn.textContent = 'Submitting…';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const uid = 'upl_' + Date.now().toString(36);\u001b[39m\n\u001b[31m    await api('/upload', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        upload_id:    uid,\u001b[39m\n\u001b[31m        uploaded_by:  myId,\u001b[39m\n\u001b[31m        list_name:    listName,\u001b[39m\n\u001b[31m        job_id:       document.getElementById('csvup-job').value.trim() || null,\u001b[39m\n\u001b[31m        source_name:  'CSV upload',\u001b[39m\n\u001b[31m        raw_rows:     _pendingUploadRows,\u001b[39m\n\u001b[31m        row_count:    _pendingUploadRows.length,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const submittedCount = _pendingUploadRows.length;\u001b[39m\n\u001b[31m    closeModal('modal-csvup');\u001b[39m\n\u001b[31m    _pendingUploadRows = [];\u001b[39m\n\u001b[31m    document.getElementById('csvup-name').value = '';\u001b[39m\n\u001b[31m    document.getElementById('csvup-job').value  = '';\u001b[39m\n\u001b[31m    toast(`Submitted ${submittedCount} rows — awaiting supervisor review`, 'ok');\u001b[39m\n\u001b[31m    await openUploads(); // refresh the queue\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Submit failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false; btn.textContent = 'Submit for Review';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Helper: parse brand_codes column ─────────────────────\u001b[39m\n\u001b[31mfunction parseBrandCodes(val) {\u001b[39m\n\u001b[31m  if (Array.isArray(val)) return val.map(b => b.toString().toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m  if (!val) return [];\u001b[39m\n\u001b[31m  return val.toString().split(',').map(b => b.toUpperCase().trim()).filter(Boolean);\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  // Adapt header title by role\u001b[39m\n\u001b[31m  const h1 = document.querySelector('#s-uploads .hdr h1');\u001b[39m\n\u001b[31m  if (h1) h1.textContent = sup ? '📥 Counter Uploads' : '📤 My Uploads';\u001b[39m\n\u001b[31m  // Supervisors review — they don't upload; hide the upload button for them\u001b[39m\n\u001b[31m  const btnUp = document.querySelector('#s-uploads .hdr-btn.p');\u001b[39m\n\u001b[31m  if (btnUp) btnUp.style.display = sup ? 'none' : '';\u001b[39m\n\u001b[31m  showScreen('s-uploads');\u001b[39m\n\u001b[31m  await loadUploads();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function loadUploads() {\u001b[39m\n\u001b[31m  document.getElementById('uploads-out').innerHTML = '<div class=\\\"empty\\\"><span class=\\\"spin\\\">↻</span> Loading…</div>';\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    // Supervisors see all pending; field reps see only their own (any status, last 30 days)\u001b[39m\n\u001b[31m    const filter = isSupervisor()\u001b[39m\n\u001b[31m      ? '/upload?approval_status=eq.pending&order=uploaded_at.desc&limit=200'\u001b[39m\n\u001b[31m      : `/upload?uploaded_by=eq.${encodeURIComponent(myId)}&order=uploaded_at.desc&limit=50`;\u001b[39m\n\u001b[31m    _uploads = await api(filter);\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Load failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m    _uploads = [];\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  // ID/Name policy (ADR-130 §5): resolve every upload's job_id to a name once\u001b[39m\n\u001b[31m  // (batched) before rendering, instead of showing the raw id.\u001b[39m\n\u001b[31m  await _ensureJobNames(_uploads.map(u => u.job_id));\u001b[39m\n\u001b[31m  renderUploads();\u001b[39m\n\u001b[31m  updateUploadBadge();\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function refreshUploadBadge() {\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?approval_status=eq.pending&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    setUploadBadge(arr.length);\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction setUploadBadge(n) {\u001b[39m\n\u001b[31m  const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m  if (!el) return;\u001b[39m\n\u001b[31m  el.textContent = n > 0 ? `Uploads (${n})` : 'Uploads';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction updateUploadBadge() { setUploadBadge((_uploads || []).length); }·\u001b[39m\n\u001b[31masync function refreshFieldRepUploadBadge() {\u001b[39m\n\u001b[31m  if (!myId) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-uploads');\u001b[39m\n\u001b[31m  if (!btn) return;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const r = await fetch(`${DB}/upload?uploaded_by=eq.${encodeURIComponent(myId)}&approval_status=eq.rejected&select=upload_id`, {\u001b[39m\n\u001b[31m      headers: { 'Accept-Profile': 'counters', 'Accept': 'application/json' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    const arr = r.ok ? await r.json() : [];\u001b[39m\n\u001b[31m    const el = document.getElementById('upload-badge');\u001b[39m\n\u001b[31m    if (!el) return;\u001b[39m\n\u001b[31m    if (arr.length > 0) {\u001b[39m\n\u001b[31m      el.textContent = `Uploads (${arr.length} ✗)`;\u001b[39m\n\u001b[31m      el.style.color = 'var(--rd, #dc2626)';\u001b[39m\n\u001b[31m      el.style.fontWeight = '600';\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (_) {}\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderUploads() {\u001b[39m\n\u001b[31m  const sup = isSupervisor();\u001b[39m\n\u001b[31m  const emptyMsg = sup\u001b[39m\n\u001b[31m    ? 'No pending uploads'\u001b[39m\n\u001b[31m    : 'No uploads yet — tap ⬆ Upload CSV above to submit a counter list';\u001b[39m\n\u001b[31m  // Update sec-label to match user role\u001b[39m\n\u001b[31m  const secLabel = document.querySelector('#s-uploads .sec-label span:first-child');\u001b[39m\n\u001b[31m  if (secLabel) secLabel.textContent = sup ? 'Awaiting supervisor review' : 'My Uploads';\u001b[39m\n\u001b[31m  document.getElementById('uploads-count').textContent =\u001b[39m\n\u001b[31m    sup ? `${_uploads.length} pending` : `${_uploads.length} submitted`;\u001b[39m\n\u001b[31m  const out = document.getElementById('uploads-out');\u001b[39m\n\u001b[31m  if (!_uploads.length) {\u001b[39m\n\u001b[31m    out.innerHTML = `<div class=\\\"empty\\\"><div class=\\\"empty-icon\\\">📥</div><div>${emptyMsg}</div></div>`;\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  out.innerHTML = _uploads.map(u => {\u001b[39m\n\u001b[31m    const statusChip = u.approval_status === 'approved'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip active\\\">APPROVED</span>`\u001b[39m\n\u001b[31m      : u.approval_status === 'rejected'\u001b[39m\n\u001b[31m      ? `<span class=\\\"status-chip inactive\\\">REJECTED</span>`\u001b[39m\n\u001b[31m      : `<span class=\\\"status-chip pending_review\\\">PENDING</span>`;\u001b[39m\n\u001b[31m    const actionBtns = sup && u.approval_status === 'pending' ? `\u001b[39m\n\u001b[31m      <div class=\\\"btn-row\\\" style=\\\"margin-top:10px\\\">\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-r\\\" onclick=\\\"rejectUpload('${esc(u.upload_id)}')\\\">Reject</button>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" onclick=\\\"approveUpload('${esc(u.upload_id)}')\\\">Approve + Create counters</button>\u001b[39m\n\u001b[31m      </div>` : u.approval_status === 'rejected' && u.approval_note\u001b[39m\n\u001b[31m      ? `<div style=\\\"font-size:12px;color:var(--rd);margin-top:6px\\\">Reason: ${esc(u.approval_note)}</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    return `\u001b[39m\n\u001b[31m    <div class=\\\"upload-row\\\" id=\\\"upl-${u.upload_id}\\\">\u001b[39m\n\u001b[31m      <div class=\\\"ur-top\\\" onclick=\\\"toggleUpload('${esc(u.upload_id)}')\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:0\\\">\u001b[39m\n\u001b[31m          <div class=\\\"ur-name\\\">${esc(u.list_name)}</div>\u001b[39m\n\u001b[31m          <div class=\\\"ur-meta\\\">\u001b[39m\n\u001b[31m            <span>By ${esc(u.uploaded_by)}</span>\u001b[39m\n\u001b[31m            <span>${esc(fmtDate(u.uploaded_at))}</span>\u001b[39m\n\u001b[31m            <span>${u.row_count} rows</span>\u001b[39m\n\u001b[31m            ${u.job_id ? `<span>Job: ${esc(_jobNameCache[u.job_id] || u.job_id)}</span>` : ''}\u001b[39m\n\u001b[31m            ${u.source_name ? `<span>${esc(u.source_name)}</span>` : ''}\u001b[39m\n\u001b[31m          </div>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        ${statusChip}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div class=\\\"ur-body\\\">\u001b[39m\n\u001b[31m        <div id=\\\"upl-hints-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        <div class=\\\"upload-rows-table\\\" id=\\\"upl-rows-${u.upload_id}\\\"></div>\u001b[39m\n\u001b[31m        ${actionBtns}\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction toggleUpload(id) {\u001b[39m\n\u001b[31m  const row = document.getElementById('upl-' + id);\u001b[39m\n\u001b[31m  if (!row) return;\u001b[39m\n\u001b[31m  const wasOpen = row.classList.contains('expanded');\u001b[39m\n\u001b[31m  // Close others\u001b[39m\n\u001b[31m  document.querySelectorAll('.upload-row.expanded').forEach(r => r.classList.remove('expanded'));\u001b[39m\n\u001b[31m  if (!wasOpen) {\u001b[39m\n\u001b[31m    row.classList.add('expanded');\u001b[39m\n\u001b[31m    fillUploadBody(id);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction fillUploadBody(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  const rows = Array.isArray(u.raw_rows) ? u.raw_rows : [];\u001b[39m\n\u001b[31m  const hints = computeBindHints(rows);\u001b[39m\n\u001b[31m  const matched   = hints.filter(h => h.matched).length;\u001b[39m\n\u001b[31m  const unmatched = hints.length - matched;·\u001b[39m\n\u001b[31m  const totalConflicts = hints.reduce((s, h) => s + (h.conflicts || []).length, 0);·\u001b[39m\n\u001b[31m  const hintsEl = document.getElementById('upl-hints-' + id);\u001b[39m\n\u001b[31m  hintsEl.innerHTML = `\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint\\\">✓ ${matched} row${matched !== 1 ? 's' : ''} match existing counters — blank fields will be filled</div>\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\">⌖ ${unmatched} row${unmatched !== 1 ? 's' : ''} will create NEW counters on approval</div>\u001b[39m\n\u001b[31m    ${totalConflicts > 0 ? `<div class=\\\"upload-match-hint\\\" style=\\\"background:rgba(249,115,22,.12);border-left-color:#f97316;color:#fed7aa\\\">⚡ ${totalConflicts} field conflict${totalConflicts !== 1 ? 's' : ''} — check boxes below to accept uploaded values (unchecked = keep existing)</div>` : ''}\u001b[39m\n\u001b[31m    <div class=\\\"upload-match-hint partial\\\" style=\\\"margin-top:6px\\\">All approved rows will auto-allocate to <b>${esc(u.uploaded_by)}</b>.</div>`;·\u001b[39m\n\u001b[31m  // Row-by-row table (better than raw JSON)\u001b[39m\n\u001b[31m  const tableEl = document.getElementById('upl-rows-' + id);\u001b[39m\n\u001b[31m  const preview = hints.slice(0, 30).map((h, i) => {\u001b[39m\n\u001b[31m    const existing = h.matched ? allCounters.find(c => c.counter_id === h.matchedId) : null;\u001b[39m\n\u001b[31m    const badge = h.matched\u001b[39m\n\u001b[31m      ? `<span style=\\\"background:#92400e;color:#fde68a;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">MATCH ${h.gstin ? 'GSTIN' : 'Name+Pin'}</span>`\u001b[39m\n\u001b[31m      : `<span style=\\\"background:#064e3b;color:#6ee7b7;padding:1px 6px;border-radius:4px;font-size:10px;font-weight:700\\\">NEW</span>`;\u001b[39m\n\u001b[31m    const name = h.name || h.raw.display_name || '—';\u001b[39m\n\u001b[31m    const city  = h.raw.city || '';\u001b[39m\n\u001b[31m    const brands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '').join(', ') || '—';\u001b[39m\n\u001b[31m    const matchInfo = existing\u001b[39m\n\u001b[31m      ? `<div style=\\\"color:#fde68a;font-size:10px;margin-top:2px\\\">→ ${esc(existing.display_name)} (${esc(existing.city || '')})</div>`\u001b[39m\n\u001b[31m      : '';\u001b[39m\n\u001b[31m    const conflictHtml = h.conflicts && h.conflicts.length ? `\u001b[39m\n\u001b[31m      <div class=\\\"upload-conflicts\\\">\u001b[39m\n\u001b[31m        <div class=\\\"uc-hdr\\\">⚡ ${h.conflicts.length} field conflict${h.conflicts.length > 1 ? 's' : ''} — check to accept uploaded value:</div>\u001b[39m\n\u001b[31m        ${h.conflicts.map(cf => `\u001b[39m\n\u001b[31m          <label class=\\\"uc-row\\\">\u001b[39m\n\u001b[31m            <input type=\\\"checkbox\\\" class=\\\"override-check\\\"\u001b[39m\n\u001b[31m              id=\\\"ov_${esc(id)}_${i}_${cf.field}\\\"\u001b[39m\n\u001b[31m              data-upload=\\\"${esc(id)}\\\" data-counter=\\\"${esc(h.matchedId)}\\\"\u001b[39m\n\u001b[31m              data-field=\\\"${cf.field}\\\" data-val=\\\"${esc(cf.uploaded)}\\\">\u001b[39m\n\u001b[31m            <div class=\\\"uc-content\\\">\u001b[39m\n\u001b[31m              <span class=\\\"uc-label\\\">${esc(cf.label)}</span>\u001b[39m\n\u001b[31m              <div class=\\\"uc-vals\\\">\u001b[39m\n\u001b[31m                <span class=\\\"uc-old\\\">Current: ${esc(cf.existing)}</span>\u001b[39m\n\u001b[31m                <span class=\\\"uc-new\\\">→ ${esc(cf.uploaded)}</span>\u001b[39m\n\u001b[31m              </div>\u001b[39m\n\u001b[31m            </div>\u001b[39m\n\u001b[31m          </label>`).join('')}\u001b[39m\n\u001b[31m      </div>` : '';\u001b[39m\n\u001b[31m    return `<div style=\\\"padding:6px 0;border-bottom:1px solid rgba(255,255,255,.05)\\\">\u001b[39m\n\u001b[31m      ${badge} <b>${esc(name)}</b> · ${esc(city)} · Brands: ${esc(brands)}\u001b[39m\n\u001b[31m      ${matchInfo}\u001b[39m\n\u001b[31m      ${conflictHtml}\u001b[39m\n\u001b[31m    </div>`;\u001b[39m\n\u001b[31m  }).join('');\u001b[39m\n\u001b[31m  tableEl.innerHTML = preview + (hints.length > 30 ? `<div style=\\\"color:var(--muted);padding:4px 0\\\">…and ${hints.length - 30} more rows</div>` : '');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction computeBindHints(rows) {\u001b[39m\n\u001b[31m  const CONFLICT_FIELDS = [\u001b[39m\n\u001b[31m    ['display_name','Name'], ['address_line1','Address 1'], ['address_line2','Address 2'],\u001b[39m\n\u001b[31m    ['city','City'], ['state','State'], ['pincode','PIN'], ['gstin','GSTIN'],\u001b[39m\n\u001b[31m    ['pan','PAN'], ['counter_type','Type'], ['channel','Channel'],\u001b[39m\n\u001b[31m    ['customer_group','Group'], ['territory','Territory'],\u001b[39m\n\u001b[31m    ['distributor_name','Distributor'], ['notes','Notes'],\u001b[39m\n\u001b[31m  ];\u001b[39m\n\u001b[31m  return rows.map(r => {\u001b[39m\n\u001b[31m    const gstin = (r.gstin || r.GSTIN || r.GSTN || '').toString().trim().toUpperCase();\u001b[39m\n\u001b[31m    const name  = (r.display_name || r.name || r.store_name || r['Store Name'] || '').toString().trim();\u001b[39m\n\u001b[31m    const pin   = (r.pincode || r.PIN || r['PIN CODE'] || '').toString().trim();\u001b[39m\n\u001b[31m    let matched = false, matchedId = null;\u001b[39m\n\u001b[31m    if (gstin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.gstin && c.gstin.toUpperCase() === gstin);\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    if (!matched && name && pin) {\u001b[39m\n\u001b[31m      const m = allCounters.find(c => c.pincode === pin && (c.display_name || '').toLowerCase() === name.toLowerCase());\u001b[39m\n\u001b[31m      if (m) { matched = true; matchedId = m.counter_id; }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const conflicts = [];\u001b[39m\n\u001b[31m    if (matched && matchedId) {\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === matchedId);\u001b[39m\n\u001b[31m      if (existing) {\u001b[39m\n\u001b[31m        for (const [field, label] of CONFLICT_FIELDS) {\u001b[39m\n\u001b[31m          const eVal = (existing[field] || '').toString().trim();\u001b[39m\n\u001b[31m          const nVal = (r[field] || '').toString().trim();\u001b[39m\n\u001b[31m          if (eVal && nVal && eVal.toLowerCase() !== nVal.toLowerCase()) {\u001b[39m\n\u001b[31m            conflicts.push({ field, label, existing: eVal, uploaded: nVal });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    return { matched, matchedId, gstin, name, pin, raw: r, conflicts };\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function approveUpload(id) {\u001b[39m\n\u001b[31m  const u = _uploads.find(x => x.upload_id === id);\u001b[39m\n\u001b[31m  if (!u) return;\u001b[39m\n\u001b[31m  // Collect override checkbox selections from rendered diff UI\u001b[39m\n\u001b[31m  const overrideMap = new Map(); // counter_id → { field: value }\u001b[39m\n\u001b[31m  document.querySelectorAll(`.override-check[data-upload=\\\"${CSS.escape(id)}\\\"]:checked`).forEach(cb => {\u001b[39m\n\u001b[31m    const cid = cb.dataset.counter;\u001b[39m\n\u001b[31m    if (!overrideMap.has(cid)) overrideMap.set(cid, {});\u001b[39m\n\u001b[31m    overrideMap.get(cid)[cb.dataset.field] = cb.dataset.val;\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  const overrideCount = [...overrideMap.values()].reduce((s, m) => s + Object.keys(m).length, 0);\u001b[39m\n\u001b[31m  const overrideNote = overrideCount > 0 ? `\\\\n${overrideCount} field override${overrideCount > 1 ? 's' : ''} will be applied to matched counters.` : '';\u001b[39m\n\u001b[31m  if (!confirm(`Approve ${u.row_count} row(s)?\\\\nNew counters will be created for unmatched rows and allocated to ${u.uploaded_by}.${overrideNote}`)) return;\u001b[39m\n\u001b[31m  const hints = computeBindHints(Array.isArray(u.raw_rows) ? u.raw_rows : []);\u001b[39m\n\u001b[31m  const newRows  = [];\u001b[39m\n\u001b[31m  const newIds   = [];\u001b[39m\n\u001b[31m  const matchIds = [];\u001b[39m\n\u001b[31m  const matchedPatches = [];  // PATCH payloads for existing matched counters\u001b[39m\n\u001b[31m  const contactRows = [];     // primary contacts to create from CSV data\u001b[39m\n\u001b[31m  hints.forEach(h => {\u001b[39m\n\u001b[31m    if (h.matched) {\u001b[39m\n\u001b[31m      matchIds.push(h.matchedId);\u001b[39m\n\u001b[31m      // Patch blank fields on the existing counter (fill-blanks strategy)\u001b[39m\n\u001b[31m      const existing = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      const patch = {};\u001b[39m\n\u001b[31m      const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m      if (!existing.legal_name       && h.raw.legal_name)       patch.legal_name       = str(h.raw.legal_name, 240);\u001b[39m\n\u001b[31m      if (!existing.proprietor_name  && h.raw.proprietor_name)  patch.proprietor_name  = str(h.raw.proprietor_name, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line1    && (h.raw.address_line1 || h.raw.address))\u001b[39m\n\u001b[31m                                                                 patch.address_line1    = str(h.raw.address_line1 || h.raw.address, 240);\u001b[39m\n\u001b[31m      if (!existing.address_line2    && h.raw.address_line2)    patch.address_line2    = str(h.raw.address_line2, 240);\u001b[39m\n\u001b[31m      if (!existing.city             && h.raw.city)             patch.city             = str(h.raw.city, 80);\u001b[39m\n\u001b[31m      if (!existing.state            && h.raw.state)            patch.state            = str(h.raw.state, 60);\u001b[39m\n\u001b[31m      if (!existing.pincode          && h.pin)                  patch.pincode          = h.pin;\u001b[39m\n\u001b[31m      if (!existing.gstin            && h.gstin)                patch.gstin            = h.gstin;\u001b[39m\n\u001b[31m      if (!existing.pan              && h.raw.pan)              patch.pan              = str(h.raw.pan, 20);\u001b[39m\n\u001b[31m      if (!existing.counter_type     && h.raw.counter_type)     patch.counter_type     = str(h.raw.counter_type, 40);\u001b[39m\n\u001b[31m      if (!existing.channel          && h.raw.channel)          patch.channel          = str(h.raw.channel, 40);\u001b[39m\n\u001b[31m      if (!existing.customer_group   && h.raw.customer_group)   patch.customer_group   = str(h.raw.customer_group, 80);\u001b[39m\n\u001b[31m      if (!existing.territory        && h.raw.territory)        patch.territory        = str(h.raw.territory, 80);\u001b[39m\n\u001b[31m      if (!existing.distributor_name && h.raw.distributor_name) patch.distributor_name = str(h.raw.distributor_name, 120);\u001b[39m\n\u001b[31m      // Merge new brand_codes (additive only)\u001b[39m\n\u001b[31m      const newBrands = parseBrandCodes(h.raw.brand_codes || h.raw.brand || '');\u001b[39m\n\u001b[31m      if (newBrands.length) {\u001b[39m\n\u001b[31m        const merged = [...new Set([...(existing.brand_codes || []), ...newBrands])];\u001b[39m\n\u001b[31m        if (merged.length !== (existing.brand_codes || []).length) patch.brand_codes = merged;\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      // Apply supervisor-approved overrides (overwrite even existing values when checkbox checked)\u001b[39m\n\u001b[31m      const overrides = overrideMap.get(h.matchedId) || {};\u001b[39m\n\u001b[31m      Object.assign(patch, overrides);\u001b[39m\n\u001b[31m      if (Object.keys(patch).length) matchedPatches.push({ counter_id: h.matchedId, patch });\u001b[39m\n\u001b[31m      // Add primary contact if CSV has one and existing counter has none\u001b[39m\n\u001b[31m      const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m      const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m      const existing2 = allCounters.find(c => c.counter_id === h.matchedId) || {};\u001b[39m\n\u001b[31m      if (cName && !existing2.primary_contact_name) {\u001b[39m\n\u001b[31m        contactRows.push({ counter_id: h.matchedId, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m      return;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    const cid = newId(h.name || 'counter');\u001b[39m\n\u001b[31m    newIds.push(cid);\u001b[39m\n\u001b[31m    const str = (v, max) => v ? v.toString().trim().slice(0, max) || null : null;\u001b[39m\n\u001b[31m    newRows.push({\u001b[39m\n\u001b[31m      counter_id:        cid,\u001b[39m\n\u001b[31m      display_name:      h.raw.display_name ? str(h.raw.display_name, 240) : (h.name || 'Unnamed'),\u001b[39m\n\u001b[31m      legal_name:        str(h.raw.legal_name, 240),\u001b[39m\n\u001b[31m      proprietor_name:   str(h.raw.proprietor_name, 240),\u001b[39m\n\u001b[31m      address_line1:     str(h.raw.address_line1 || h.raw.address || '—', 240),\u001b[39m\n\u001b[31m      address_line2:     str(h.raw.address_line2, 240),\u001b[39m\n\u001b[31m      city:              str(h.raw.city, 80),\u001b[39m\n\u001b[31m      state:             str(h.raw.state, 60),\u001b[39m\n\u001b[31m      pincode:           h.pin || null,\u001b[39m\n\u001b[31m      gstin:             h.gstin || null,\u001b[39m\n\u001b[31m      pan:               str(h.raw.pan, 20),\u001b[39m\n\u001b[31m      fssai:             str(h.raw.fssai, 30),\u001b[39m\n\u001b[31m      drug_lic:          str(h.raw.drug_lic, 60),\u001b[39m\n\u001b[31m      counter_type:      str(h.raw.counter_type, 40),\u001b[39m\n\u001b[31m      channel:           str(h.raw.channel, 40),\u001b[39m\n\u001b[31m      customer_group:    str(h.raw.customer_group, 80),\u001b[39m\n\u001b[31m      territory:         str(h.raw.territory, 80),\u001b[39m\n\u001b[31m      distributor_name:  str(h.raw.distributor_name, 120),\u001b[39m\n\u001b[31m      notes:             str(h.raw.notes, 500),\u001b[39m\n\u001b[31m      brand_codes:       parseBrandCodes(h.raw.brand_codes || h.raw.brand || ''),\u001b[39m\n\u001b[31m      created_by:        myId,\u001b[39m\n\u001b[31m      created_from:      'upload',\u001b[39m\n\u001b[31m      approval_status:   'approved',\u001b[39m\n\u001b[31m      approved_by:       myId,\u001b[39m\n\u001b[31m      approved_at:       new Date().toISOString(),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    // Primary contact from CSV if provided\u001b[39m\n\u001b[31m    const cName = str(h.raw.primary_contact_name || h.raw.contact_name, 120);\u001b[39m\n\u001b[31m    const cPhone = str(h.raw.primary_contact_phone || h.raw.contact_phone || h.raw.mobile, 30);\u001b[39m\n\u001b[31m    if (cName) contactRows.push({ counter_id: cid, role: 'primary', name: cName, phone: cPhone || null, is_primary: true });\u001b[39m\n\u001b[31m  });\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    if (newRows.length) {\u001b[39m\n\u001b[31m      await api('/counter', { method: 'POST', body: JSON.stringify(newRows), headers: { Prefer: 'return=minimal' } });\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Create primary contacts for new counters (and fill-blanks for matched counters without a contact)\u001b[39m\n\u001b[31m    if (contactRows.length) {\u001b[39m\n\u001b[31m      await api('/contact', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(contactRows), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Patch blank fields on matched counters (fill-blanks + brand merge)\u001b[39m\n\u001b[31m    for (const mp of matchedPatches) {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(mp.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', body: JSON.stringify(mp.patch), headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m      }).catch(() => {});\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Write brand associations to counter_brand table (if migration v19 has been run)\u001b[39m\n\u001b[31m    const brandAssocRows = [\u001b[39m\n\u001b[31m      ...newRows.flatMap(r => (r.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: r.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m      ...matchedPatches.flatMap(mp => (mp.patch.brand_codes || []).map(bc => ({\u001b[39m\n\u001b[31m        counter_id: mp.counter_id, brand_code: bc, source: 'upload', added_by: myId,\u001b[39m\n\u001b[31m      }))),\u001b[39m\n\u001b[31m    ];\u001b[39m\n\u001b[31m    if (brandAssocRows.length) {\u001b[39m\n\u001b[31m      await api('/counter_brand', {\u001b[39m\n\u001b[31m        method: 'POST', body: JSON.stringify(brandAssocRows),\u001b[39m\n\u001b[31m        headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' },\u001b[39m\n\u001b[31m      }).catch(() => {}); // silently ignore if table doesn't exist yet\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Auto-allocate ALL touched counters to the uploader\u001b[39m\n\u001b[31m    const allCids = [...matchIds, ...newIds];\u001b[39m\n\u001b[31m    if (allCids.length) {\u001b[39m\n\u001b[31m      const rows = allCids.map(cid => ({\u001b[39m\n\u001b[31m        employee_id: u.uploaded_by, counter_id: cid, job_id: u.job_id || null,\u001b[39m\n\u001b[31m        allocated_by: myId, source: 'upload_auto',\u001b[39m\n\u001b[31m      }));\u001b[39m\n\u001b[31m      // Use ON CONFLICT via duplicate-tolerant approach — try insert; ignore duplicate errors silently\u001b[39m\n\u001b[31m      try {\u001b[39m\n\u001b[31m        await api('/allocation', { method: 'POST', body: JSON.stringify(rows), headers: { Prefer: 'return=minimal,resolution=ignore-duplicates' } });\u001b[39m\n\u001b[31m      } catch (_) { /* most duplicates surface via index conflict — silenced */ }\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m    // Unblock submissions waiting on this upload\u001b[39m\n\u001b[31m    await api(`/submissions?pending_upload_id=eq.${id}`, {\u001b[39m\n\u001b[31m      method: 'PATCH', body: JSON.stringify({ pending_supervisor_approval: false, pending_upload_id: null }),\u001b[39m\n\u001b[31m      headers: { 'Content-Profile': 'recce', 'Accept-Profile': 'recce', Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    }).catch(() => {});\u001b[39m\n\u001b[31m    // Mark upload approved\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'approved', approved_by: myId, approved_at: new Date().toISOString(),\u001b[39m\n\u001b[31m        promoted_counter_ids: newIds, matched_counter_ids: matchIds,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast(`Approved — ${newIds.length} new, ${matchIds.length} matched`, 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m    await loadList();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Approve failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function rejectUpload(id) {\u001b[39m\n\u001b[31m  const note = prompt('Optional reason for rejection (blank to skip):', '') || null;\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    await api(`/upload?upload_id=eq.${encodeURIComponent(id)}`, {\u001b[39m\n\u001b[31m      method: 'PATCH',\u001b[39m\n\u001b[31m      body: JSON.stringify({\u001b[39m\n\u001b[31m        approval_status: 'rejected', approved_by: myId, approved_at: new Date().toISOString(), approval_note: note,\u001b[39m\n\u001b[31m      }),\u001b[39m\n\u001b[31m      headers: { Prefer: 'return=minimal' },\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    toast('Rejected', 'ok');\u001b[39m\n\u001b[31m    await loadUploads();\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    toast('Reject failed: ' + e.message, 'err');\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Update banner + SW ────────────────────────────────────\u001b[39m\n\u001b[31mfunction checkUpdate() {\u001b[39m\n\u001b[31m  const seen = localStorage.getItem('counters-ver-seen');\u001b[39m\n\u001b[31m  localStorage.setItem('counters-ver-seen', CACHE_VER);\u001b[39m\n\u001b[31m  if (seen && seen !== CACHE_VER) document.getElementById('upd-banner').classList.add('show');\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction registerSW() {\u001b[39m\n\u001b[31m  if (!('serviceWorker' in navigator)) return;\u001b[39m\n\u001b[31m  const sw = `\u001b[39m\n\u001b[31mconst C='${CACHE_VER}';\u001b[39m\n\u001b[31mself.addEventListener('install',e=>{self.skipWaiting();});\u001b[39m\n\u001b[31mself.addEventListener('activate',e=>{\u001b[39m\n\u001b[31m  e.waitUntil(caches.keys().then(ks=>Promise.all(ks.filter(k=>k!==C).map(k=>caches.delete(k)))));\u001b[39m\n\u001b[31m  self.clients.claim();\u001b[39m\n\u001b[31m});\u001b[39m\n\u001b[31mself.addEventListener('fetch',e=>{\u001b[39m\n\u001b[31m  if(e.request.method!=='GET')return;\u001b[39m\n\u001b[31m  const url=new URL(e.request.url);\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/db'))return;\u001b[39m\n\u001b[31m  if(url.pathname.startsWith('/counters/')){\u001b[39m\n\u001b[31m    e.respondWith(\u001b[39m\n\u001b[31m      caches.open(C).then(c=>c.match(e.request).then(r=>r||fetch(e.request).then(f=>{c.put(e.request,f.clone());return f;})))\u001b[39m\n\u001b[31m    );\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m});`;\u001b[39m\n\u001b[31m  const blob = new Blob([sw], { type: 'application/javascript' });\u001b[39m\n\u001b[31m  navigator.serviceWorker.register(URL.createObjectURL(blob), { scope: '/counters/' }).catch(() => {});\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Boot ──────────────────────────────────────────────────\u001b[39m\n\u001b[31masync function boot() {\u001b[39m\n\u001b[31m  if (!bootSession()) return;\u001b[39m\n\u001b[31m  checkUpdate();\u001b[39m\n\u001b[31m  registerSW();\u001b[39m\n\u001b[31m  showScreen('s-list');\u001b[39m\n\u001b[31m  await loadList();\u001b[39m\n\u001b[31m  if (isSupervisor()) { refreshUploadBadge(); refreshMultiRecceBadge(); refreshClientsBadge(); refreshNotifyBadge(); refreshAuditBadge(); }\u001b[39m\n\u001b[31m  else { refreshFieldRepUploadBadge(); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?counter_id=XXX opens that counter directly\u001b[39m\n\u001b[31m  const _dlParams = new URLSearchParams(location.search);\u001b[39m\n\u001b[31m  const _dlId = _dlParams.get('counter_id');\u001b[39m\n\u001b[31m  if (_dlId) { history.replaceState({}, '', location.pathname); openDetail(_dlId); }\u001b[39m\n\u001b[31m  // Deep-link: /counters/?new=1&name=...&city=...&address=...&mobile=... opens New Counter pre-filled\u001b[39m\n\u001b[31m  else if (_dlParams.get('new') === '1' && isSupervisor()) {\u001b[39m\n\u001b[31m    history.replaceState({}, '', location.pathname);\u001b[39m\n\u001b[31m    openNew({\u001b[39m\n\u001b[31m      name:    _dlParams.get('name')    || '',\u001b[39m\n\u001b[31m      address: _dlParams.get('address') || '',\u001b[39m\n\u001b[31m      city:    _dlParams.get('city')    || '',\u001b[39m\n\u001b[31m      mobile:  _dlParams.get('mobile')  || '',\u001b[39m\n\u001b[31m      notes:   _dlParams.get('notes')   || '',\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31m// ── Onboarding tour ──────────────────────────────────────────\u001b[39m\n\u001b[31mconst TOUR_STEPS = [\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🗺️',\u001b[39m\n\u001b[31m    title: 'Supervisor Guide',\u001b[39m\n\u001b[31m    body: 'This tour walks through the full client-allotment journey: managing counters, setting up brand clients, inviting client users, and the client portal.\\\\n\\\\nTap Next to continue or Skip tour to close.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'The Counter List',\u001b[39m\n\u001b[31m    body: 'The main screen shows all your counters. Use the filter tiles (All / Active / Pending / Closed) and the search bar to find any outlet quickly. Tap any row to open its detail.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏪',\u001b[39m\n\u001b[31m    title: 'Counter Detail',\u001b[39m\n\u001b[31m    body: 'Each counter profile shows address, territory, GPS pin, brand affiliations, contacts, and all past Recce visits. Supervisors can allocate agents and edit counter info here.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '➕',\u001b[39m\n\u001b[31m    title: 'Adding a New Counter',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ New\\\" in the header to register a new outlet. Fill in the display name, address, territory, GPS co-ordinates, and brand codes. Every Recce visit is linked back to a counter.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-list')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏷️',\u001b[39m\n\u001b[31m    title: 'Brand Clients',\u001b[39m\n\u001b[31m    body: 'Tap \\\"🏷 Clients\\\" in the header to open the Clients section — visible to supervisors only. Each client represents a brand company (like HP, Lenovo, or KFC) whose products are placed in your counters.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🏢',\u001b[39m\n\u001b[31m    title: 'What Is a Client?',\u001b[39m\n\u001b[31m    body: 'A client has a name, one or more brand codes (e.g. HP), and an optional contact email.\\\\n\\\\nBrand codes must match exactly what agents enter in Recce submissions — this is how the portal filters which Recces each client can see.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🆕',\u001b[39m\n\u001b[31m    title: 'Creating a New Client',\u001b[39m\n\u001b[31m    body: 'Tap \\\"+ Client\\\" to register a brand client. Set the brand code carefully — capitalisation must match the brand used in Recce submissions (e.g. \\\"HP\\\" not \\\"hp\\\"). Multiple brand codes are allowed.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✉️',\u001b[39m\n\u001b[31m    title: 'Inviting a Client User',\u001b[39m\n\u001b[31m    body: 'Expand any client row and tap \\\"✉ Invite user\\\". Enter the person\\\\'s email, choose a role (SM, ASM, etc.), and set their scope.\\\\n\\\\nA magic link is sent to their email and stays valid for 7 days.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔭',\u001b[39m\n\u001b[31m    title: 'Controlling What Clients See',\u001b[39m\n\u001b[31m    body: 'Scope decides which Recces a client user can access:\\\\n• All — every Recce for their brand\\\\n• By territory — specific zones only\\\\n• By job_id — a specific campaign\\\\n• By Recce ID — handpick individual reports\\\\n\\\\nUse \\\"All\\\" when counters have no territory assigned.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-clients')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🌐',\u001b[39m\n\u001b[31m    title: 'The Client Portal',\u001b[39m\n\u001b[31m    body: 'When the client opens their magic link they land on the Recce Client Portal (/recce-client/). They can approve, request changes, or reject each Recce.\\\\n\\\\nResponses are saved instantly and visible to supervisors in the DB.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🤖',\u001b[39m\n\u001b[31m    title: 'AI Import — Bulk Add Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"🤖 AI Import\\\" button (Uploads screen, supervisors only) converts any raw store list — WhatsApp forwards, Excel paste, typed notes — into the counter upload format automatically.\\\\n\\\\nNo CSV template needed: just paste and convert.',\u001b[39m\n\u001b[31m    screenFn: () => showScreen('s-uploads')\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '📋',\u001b[39m\n\u001b[31m    title: 'Paste Any Format',\u001b[39m\n\u001b[31m    body: 'AI Import accepts virtually any layout:\\\\n• WhatsApp-forwarded shop lists\\\\n• Excel copy-paste (columns or rows)\\\\n• Typed lists with mixed formats\\\\n• GSTIN / phone / pincode scattered anywhere\\\\n\\\\nPaste the raw text into the box and tap \\\"Convert with AI\\\".',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'AI Extracts Every Field',\u001b[39m\n\u001b[31m    body: 'The AI picks out:\\\\n• Store name, owner, address\\\\n• City, state (abbreviations expanded)\\\\n• Pincode, GSTIN, PAN\\\\n• Phone (stripped to 10 digits)\\\\n• Counter type inferred from context\\\\n\\\\nConverts 3–50 stores in 15–30 seconds via OAuth.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '✅',\u001b[39m\n\u001b[31m    title: 'Review Before Upload',\u001b[39m\n\u001b[31m    body: 'After conversion, a preview shows every extracted row — name, city, state, phone, counter type.\\\\n\\\\nCheck the preview, then tap \\\"Use these rows →\\\" to feed them into the existing upload queue. A supervisor approves the batch before counters are created.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-uploads'); openAiImport(); }\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '🔑',\u001b[39m\n\u001b[31m    title: 'AI Route & Fallback',\u001b[39m\n\u001b[31m    body: 'AI Import tries routes in order:\\\\n1️⃣ Claude OAuth (free, via Claude Code on the server)\\\\n2️⃣ OpenRouter (free-tier models as backup)\\\\n3️⃣ Anthropic API key — only if you explicitly approve when prompted\\\\n\\\\nFor large lists (50+ stores) it splits into batches automatically.'\u001b[39m\n\u001b[31m  },\u001b[39m\n\u001b[31m  {\u001b[39m\n\u001b[31m    emoji: '⚡',\u001b[39m\n\u001b[31m    title: 'Bulk Edit Counters',\u001b[39m\n\u001b[31m    body: 'The \\\"⚡ Bulk\\\" button (supervisor only) applies a change to all counters matching your current filter in one shot.\\\\n\\\\nUse it to:\\\\n• Assign a brand code to hundreds of counters at once\\\\n• Set counter type or territory across a filtered set\\\\n• No existing values are overwritten — brands are merged, other fields fill blanks only.',\u001b[39m\n\u001b[31m    screenFn: () => { showScreen('s-list'); openBulkEdit(); }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m];·\u001b[39m\n\u001b[31minitTour(TOUR_STEPS, { storageKey: 'tour-counters-v1', autoShow: false });·\u001b[39m\n\u001b[31mboot();·\u001b[39m\n\u001b[31m// ── AI Geo-fill ────────────────────────────────────────────────────────────\u001b[39m\n\u001b[31mconst STATE_CODE_MAP = {\u001b[39m\n\u001b[31m  'andhra pradesh':'AP','arunachal pradesh':'AR','assam':'AS','bihar':'BR',\u001b[39m\n\u001b[31m  'chandigarh':'CH','chhattisgarh':'CG','delhi':'DL','goa':'GA','gujarat':'GJ',\u001b[39m\n\u001b[31m  'haryana':'HR','himachal':'HP','himachal pradesh':'HP','j&k':'JK',\u001b[39m\n\u001b[31m  'jammu & kashmir':'JK','jammu and kashmir':'JK','jharkhand':'JH',\u001b[39m\n\u001b[31m  'karnataka':'KA','kerala':'KL','ladakh':'LA','madhya pradesh':'MP',\u001b[39m\n\u001b[31m  'maharashtra':'MH','manipur':'MN','meghalaya':'ML','mizoram':'MZ',\u001b[39m\n\u001b[31m  'nagaland':'NL','odisha':'OD','puducherry':'PY','punjab':'PB',\u001b[39m\n\u001b[31m  'rajasthan':'RJ','sikkim':'SK','tamil nadu':'TN','telangana':'TS',\u001b[39m\n\u001b[31m  'tripura':'TR','uttar pradesh':'UP','uttarakhand':'UK','west bengal':'WB',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mconst CITY_TIER_MAP = {\u001b[39m\n\u001b[31m  // Tier 1 – national metros\u001b[39m\n\u001b[31m  'delhi':'tier1','mumbai':'tier1','bangalore':'tier1','bengaluru':'tier1',\u001b[39m\n\u001b[31m  'chennai':'tier1','kolkata':'tier1','hyderabad':'tier1','pune':'tier1','ahmedabad':'tier1',\u001b[39m\n\u001b[31m  // Tier 2 – state capitals + major cities\u001b[39m\n\u001b[31m  'chandigarh':'tier2','ludhiana':'tier2','amritsar':'tier2','jalandhar':'tier2',\u001b[39m\n\u001b[31m  'patiala':'tier2','faridabad':'tier2','gurugram':'tier2','gurgaon':'tier2',\u001b[39m\n\u001b[31m  'rohtak':'tier2','hisar':'tier2','ambala':'tier2','karnal':'tier2','panipat':'tier2',\u001b[39m\n\u001b[31m  'shimla':'tier2','jammu':'tier2','srinagar':'tier2','dehradun':'tier2',\u001b[39m\n\u001b[31m  'jaipur':'tier2','lucknow':'tier2','bhopal':'tier2','indore':'tier2',\u001b[39m\n\u001b[31m  'nagpur':'tier2','coimbatore':'tier2','visakhapatnam':'tier2','kochi':'tier2',\u001b[39m\n\u001b[31m  'surat':'tier2','vadodara':'tier2','rajkot':'tier2','patna':'tier2',\u001b[39m\n\u001b[31m  // Tier 3 – district HQs / medium towns\u001b[39m\n\u001b[31m  'bathinda':'tier3','moga':'tier3','hoshiarpur':'tier3','gurdaspur':'tier3',\u001b[39m\n\u001b[31m  'sangrur':'tier3','ferozepur':'tier3','muktsar':'tier3','nawanshahr':'tier3',\u001b[39m\n\u001b[31m  'rupnagar':'tier3','ropar':'tier3','barnala':'tier3','mansa':'tier3',\u001b[39m\n\u001b[31m  'mohali':'tier3','sas nagar':'tier3','panchkula':'tier3','sonipat':'tier3',\u001b[39m\n\u001b[31m  'bhiwani':'tier3','jind':'tier3','kaithal':'tier3','kurukshetra':'tier3',\u001b[39m\n\u001b[31m  'sirsa':'tier3','rewari':'tier3','palwal':'tier3','nuh':'tier3',\u001b[39m\n\u001b[31m  'solan':'tier3','mandi':'tier3','dharamshala':'tier3','kangra':'tier3',\u001b[39m\n\u001b[31m  'kullu':'tier3','bilaspur':'tier3','hamirpur':'tier3','una':'tier3',\u001b[39m\n\u001b[31m  'chamba':'tier3','palampur':'tier3','nurpur':'tier3','baddi':'tier3',\u001b[39m\n\u001b[31m  'baramullah':'tier3','baramulla':'tier3','anantnag':'tier3','pulwama':'tier3',\u001b[39m\n\u001b[31m  'kathua':'tier3','udhampur':'tier3','rajouri':'tier3',\u001b[39m\n\u001b[31m};·\u001b[39m\n\u001b[31mlet _geoSuggestions = []; // {counter_id, display_name, city, state, cur_sc, sug_sc, cur_tier, sug_tier, cur_cluster, sug_cluster, source, checked}·\u001b[39m\n\u001b[31mfunction gfCounterPick() {\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const c   = allCounters.find(x => x.counter_id === sel.value);\u001b[39m\n\u001b[31m  if (c) {\u001b[39m\n\u001b[31m    document.getElementById('gf-city').value  = c.city  || '';\u001b[39m\n\u001b[31m    document.getElementById('gf-state').value = c.state || '';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function gfAskAI() {\u001b[39m\n\u001b[31m  const sel     = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  const city    = document.getElementById('gf-city').value.trim();\u001b[39m\n\u001b[31m  const state   = document.getElementById('gf-state').value.trim();\u001b[39m\n\u001b[31m  const statusEl= document.getElementById('gf-ask-status');\u001b[39m\n\u001b[31m  const btn     = document.getElementById('btn-gf-ask');\u001b[39m\n\u001b[31m  const cid     = sel.value;\u001b[39m\n\u001b[31m  const cName   = sel.options[sel.selectedIndex]?.text || cid;·\u001b[39m\n\u001b[31m  if (!cid)  { statusEl.textContent = '⚠ Select a counter first'; return; }\u001b[39m\n\u001b[31m  if (!city) { statusEl.textContent = '⚠ Enter a city name'; return; }·\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = '⟳ Asking…';\u001b[39m\n\u001b[31m  statusEl.style.color = 'var(--muted)';\u001b[39m\n\u001b[31m  statusEl.textContent = 'Sending to AI…';·\u001b[39m\n\u001b[31m  try {\u001b[39m\n\u001b[31m    const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m      method: 'POST',\u001b[39m\n\u001b[31m      headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m      body: JSON.stringify({ counters: [{ counter_id: cid, display_name: cName, city, state }] }),\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);\u001b[39m\n\u001b[31m    const data = await resp.json();\u001b[39m\n\u001b[31m    const sug  = (data.suggestions || [])[0];\u001b[39m\n\u001b[31m    if (!sug) throw new Error('No suggestion returned');·\u001b[39m\n\u001b[31m    const c = allCounters.find(x => x.counter_id === cid);\u001b[39m\n\u001b[31m    const needsSC   = sug.state_code  && sug.state_code  !== (c?.state_code  || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sug.city_tier   && sug.city_tier   !== (c?.city_tier   || '');\u001b[39m\n\u001b[31m    const needsClu  = sug.geo_cluster && sug.geo_cluster !== (c?.geo_cluster  || '');\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier && !needsClu) {\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = '✓ AI agrees — no changes needed for this counter';\u001b[39m\n\u001b[31m    } else {\u001b[39m\n\u001b[31m      // Remove any existing suggestion for this counter, then add fresh AI one\u001b[39m\n\u001b[31m      _geoSuggestions = _geoSuggestions.filter(s => s.counter_id !== cid);\u001b[39m\n\u001b[31m      _geoSuggestions.push({\u001b[39m\n\u001b[31m        counter_id: cid, display_name: cName, city, state,\u001b[39m\n\u001b[31m        cur_sc: c?.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m        cur_tier: c?.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m        cur_cluster: c?.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m        source: 'ai', checked: true,\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      renderGeoFillTable();\u001b[39m\n\u001b[31m      document.getElementById('btn-geofill-apply').disabled = false;\u001b[39m\n\u001b[31m      statusEl.style.color = 'var(--g)';\u001b[39m\n\u001b[31m      statusEl.textContent = `✓ Added suggestion for ${cName}`;\u001b[39m\n\u001b[31m    }\u001b[39m\n\u001b[31m  } catch (e) {\u001b[39m\n\u001b[31m    statusEl.style.color = 'var(--err)';\u001b[39m\n\u001b[31m    statusEl.textContent = '⚠ AI call failed: ' + e.message;\u001b[39m\n\u001b[31m  } finally {\u001b[39m\n\u001b[31m    btn.disabled = false;\u001b[39m\n\u001b[31m    btn.textContent = 'Ask AI';\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function openGeoFill() {\u001b[39m\n\u001b[31m  _geoSuggestions = [];\u001b[39m\n\u001b[31m  const el    = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const stEl  = document.getElementById('geofill-status');\u001b[39m\n\u001b[31m  const apBtn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  el.innerHTML = '<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">Scanning counters…</div>';\u001b[39m\n\u001b[31m  stEl.textContent = '';\u001b[39m\n\u001b[31m  apBtn.disabled = true;\u001b[39m\n\u001b[31m  document.getElementById('gf-ask-status').textContent = '';\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').disabled = false;\u001b[39m\n\u001b[31m  document.getElementById('btn-gf-ask').textContent = 'Ask AI';·\u001b[39m\n\u001b[31m  // Populate counter dropdown\u001b[39m\n\u001b[31m  const sel = document.getElementById('gf-counter-sel');\u001b[39m\n\u001b[31m  sel.innerHTML = '<option value=\\\"\\\">— pick a counter —</option>' +\u001b[39m\n\u001b[31m    allCounters\u001b[39m\n\u001b[31m      .filter(c => c.city)\u001b[39m\n\u001b[31m      .sort((a, b) => a.display_name.localeCompare(b.display_name))\u001b[39m\n\u001b[31m      .map(c => `<option value=\\\"${esc(c.counter_id)}\\\">${esc(c.display_name)} · ${esc(c.city||'')}${c.state?', '+esc(c.state):''}</option>`)\u001b[39m\n\u001b[31m      .join('');\u001b[39m\n\u001b[31m  document.getElementById('gf-city').value  = '';\u001b[39m\n\u001b[31m  document.getElementById('gf-state').value = '';·\u001b[39m\n\u001b[31m  document.getElementById('modal-geofill').classList.add('show');·\u001b[39m\n\u001b[31m  // ── Pass 1: static lookup (instant) ──────────────────────\u001b[39m\n\u001b[31m  for (const c of allCounters) {\u001b[39m\n\u001b[31m    const stateKey  = (c.state || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const cityKey   = (c.city  || '').toLowerCase().trim();\u001b[39m\n\u001b[31m    const sugSC     = STATE_CODE_MAP[stateKey]  || null;\u001b[39m\n\u001b[31m    const sugTier   = CITY_TIER_MAP[cityKey]    || null;\u001b[39m\n\u001b[31m    const needsSC   = sugSC   && sugSC   !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m    const needsTier = sugTier && sugTier !== c.city_tier;\u001b[39m\n\u001b[31m    if (!needsSC && !needsTier) continue;\u001b[39m\n\u001b[31m    _geoSuggestions.push({\u001b[39m\n\u001b[31m      counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m      cur_sc: c.state_code || '', sug_sc: needsSC ? sugSC : null,\u001b[39m\n\u001b[31m      cur_tier: c.city_tier || '', sug_tier: needsTier ? sugTier : null,\u001b[39m\n\u001b[31m      cur_cluster: c.geo_cluster || '', sug_cluster: null,\u001b[39m\n\u001b[31m      source: 'static', checked: true,\u001b[39m\n\u001b[31m    });\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  // ── Pass 2: AI for counters missing geo_cluster ───────────\u001b[39m\n\u001b[31m  const aiTargets = allCounters.filter(c => c.city && !c.geo_cluster);\u001b[39m\n\u001b[31m  if (aiTargets.length) {\u001b[39m\n\u001b[31m    el.innerHTML = `<div style=\\\"color:var(--muted);font-size:12px;padding:12px\\\">\u001b[39m\n\u001b[31m      ✨ Pass 1 done (${_geoSuggestions.length} static). 🤖 Asking AI for geo_cluster on ${aiTargets.length} counters…\u001b[39m\n\u001b[31m      <span style=\\\"display:inline-block;margin-left:6px;animation:spin 1s linear infinite\\\">⟳</span></div>`;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const resp = await fetch(AI_CONVERT_URL + 'geo', {\u001b[39m\n\u001b[31m        method: 'POST',\u001b[39m\n\u001b[31m        headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + AI_CONVERT_SECRET },\u001b[39m\n\u001b[31m        body: JSON.stringify({ counters: aiTargets.map(c => ({ counter_id: c.counter_id, display_name: c.display_name, city: c.city || '', state: c.state || '' })) }),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      if (resp.ok) {\u001b[39m\n\u001b[31m        const data = await resp.json();\u001b[39m\n\u001b[31m        for (const sug of (data.suggestions || [])) {\u001b[39m\n\u001b[31m          if (!sug.geo_cluster && !sug.state_code && !sug.city_tier) continue;\u001b[39m\n\u001b[31m          const c = allCounters.find(x => x.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (!c) continue;\u001b[39m\n\u001b[31m          const existing = _geoSuggestions.find(s => s.counter_id === sug.counter_id);\u001b[39m\n\u001b[31m          if (existing) {\u001b[39m\n\u001b[31m            if (sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '')) existing.sug_cluster = sug.geo_cluster;\u001b[39m\n\u001b[31m          } else {\u001b[39m\n\u001b[31m            const needsSC  = sug.state_code  && sug.state_code  !== (c.state_code || '').toUpperCase();\u001b[39m\n\u001b[31m            const needsTier= sug.city_tier   && sug.city_tier   !== c.city_tier;\u001b[39m\n\u001b[31m            const needsClu = sug.geo_cluster && sug.geo_cluster !== (c.geo_cluster || '');\u001b[39m\n\u001b[31m            if (!needsSC && !needsTier && !needsClu) continue;\u001b[39m\n\u001b[31m            _geoSuggestions.push({\u001b[39m\n\u001b[31m              counter_id: c.counter_id, display_name: c.display_name, city: c.city, state: c.state,\u001b[39m\n\u001b[31m              cur_sc: c.state_code || '', sug_sc: needsSC ? sug.state_code : null,\u001b[39m\n\u001b[31m              cur_tier: c.city_tier || '', sug_tier: needsTier ? sug.city_tier : null,\u001b[39m\n\u001b[31m              cur_cluster: c.geo_cluster || '', sug_cluster: needsClu ? sug.geo_cluster : null,\u001b[39m\n\u001b[31m              source: 'ai', checked: true,\u001b[39m\n\u001b[31m            });\u001b[39m\n\u001b[31m          }\u001b[39m\n\u001b[31m        }\u001b[39m\n\u001b[31m      }\u001b[39m\n\u001b[31m    } catch (e) { console.warn('[geo-fill] AI pass failed:', e); }\u001b[39m\n\u001b[31m  }·\u001b[39m\n\u001b[31m  if (!_geoSuggestions.length) {\u001b[39m\n\u001b[31m    el.innerHTML = '<div style=\\\"color:var(--g);font-size:12px;padding:12px\\\">✓ All counters already have up-to-date geo fields.</div>';\u001b[39m\n\u001b[31m    return;\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  renderGeoFillTable();\u001b[39m\n\u001b[31m  apBtn.disabled = false;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction renderGeoFillTable() {\u001b[39m\n\u001b[31m  const el = document.getElementById('geofill-table');\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  const diffCell = (cur, sug) => sug\u001b[39m\n\u001b[31m    ? `<span style=\\\"color:var(--muted);text-decoration:line-through\\\">${esc(cur)||'—'}</span> → <b style=\\\"color:var(--g)\\\">${esc(sug)}</b>`\u001b[39m\n\u001b[31m    : `<span style=\\\"color:var(--muted)\\\">${esc(cur)||'—'}</span>`;\u001b[39m\n\u001b[31m  el.innerHTML = `<table style=\\\"width:100%;border-collapse:collapse;font-size:11px\\\">\u001b[39m\n\u001b[31m    <thead><tr style=\\\"background:var(--card2)\\\">\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">✓</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Counter</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">City / State</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">SC</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Tier</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Cluster</th>\u001b[39m\n\u001b[31m      <th style=\\\"padding:5px 4px;text-align:left;color:var(--muted)\\\">Src</th>\u001b[39m\n\u001b[31m    </tr></thead><tbody>` +\u001b[39m\n\u001b[31m    _geoSuggestions.map((s, i) => `\u001b[39m\n\u001b[31m      <tr style=\\\"border-top:1px solid var(--border)\\\">\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><input type=\\\"checkbox\\\" ${s.checked ? 'checked' : ''} onchange=\\\"geoToggle(${i},this.checked)\\\"></td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;font-weight:600;max-width:120px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap\\\">${esc(s.display_name)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px;color:var(--muted);white-space:nowrap\\\">${esc(s.city||'')}${s.state?', '+esc(s.state):''}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_sc, s.sug_sc)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_tier, s.sug_tier)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\">${diffCell(s.cur_cluster, s.sug_cluster)}</td>\u001b[39m\n\u001b[31m        <td style=\\\"padding:4px\\\"><span style=\\\"font-size:10px;padding:1px 5px;border-radius:10px;background:${s.source==='ai'?'var(--accent)':'var(--card2)'};color:${s.source==='ai'?'#fff':'var(--muted)'}\\\">${s.source==='ai'?'🤖':'✨'}</span></td>\u001b[39m\n\u001b[31m      </tr>`).join('') +\u001b[39m\n\u001b[31m    '</tbody></table>';\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31mfunction geoToggle(i, v) {\u001b[39m\n\u001b[31m  _geoSuggestions[i].checked = v;\u001b[39m\n\u001b[31m  const checked = _geoSuggestions.filter(s => s.checked).length;\u001b[39m\n\u001b[31m  document.getElementById('geofill-status').textContent = `${checked} of ${_geoSuggestions.length} changes selected`;\u001b[39m\n\u001b[31m  document.getElementById('btn-geofill-apply').disabled = checked === 0;\u001b[39m\n\u001b[31m}·\u001b[39m\n\u001b[31masync function applyGeoFill() {\u001b[39m\n\u001b[31m  const targets = _geoSuggestions.filter(s => s.checked);\u001b[39m\n\u001b[31m  if (!targets.length) return;\u001b[39m\n\u001b[31m  const btn = document.getElementById('btn-geofill-apply');\u001b[39m\n\u001b[31m  btn.disabled = true;\u001b[39m\n\u001b[31m  btn.textContent = 'Applying…';\u001b[39m\n\u001b[31m  let ok = 0, fail = 0;\u001b[39m\n\u001b[31m  for (const s of targets) {\u001b[39m\n\u001b[31m    const patch = {};\u001b[39m\n\u001b[31m    if (s.sug_sc)      patch.state_code  = s.sug_sc;\u001b[39m\n\u001b[31m    if (s.sug_tier)    patch.city_tier   = s.sug_tier;\u001b[39m\n\u001b[31m    if (s.sug_cluster) patch.geo_cluster = s.sug_cluster;\u001b[39m\n\u001b[31m    if (!Object.keys(patch).length) continue;\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      await api(`/counter?counter_id=eq.${encodeURIComponent(s.counter_id)}`, {\u001b[39m\n\u001b[31m        method: 'PATCH', headers: { Prefer: 'return=minimal' }, body: JSON.stringify(patch),\u001b[39m\n\u001b[31m      });\u001b[39m\n\u001b[31m      ok++;\u001b[39m\n\u001b[31m      const c = allCounters.find(x => x.counter_id === s.counter_id);\u001b[39m\n\u001b[31m      if (c) { if (patch.state_code) c.state_code = patch.state_code; if (patch.city_tier) c.city_tier = patch.city_tier; if (patch.geo_cluster) c.geo_cluster = patch.geo_cluster; }\u001b[39m\n\u001b[31m    } catch { fail++; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  closeModal('modal-geofill');\u001b[39m\n\u001b[31m  toast(`Geo-fill done — ${ok} updated${fail ? `, ${fail} failed` : ''}`, ok ? 'ok' : 'err');\u001b[39m\n\u001b[31m  if (ok) renderList();\u001b[39m\n\u001b[31m}\u001b[39m\n\u001b[31m</script>·\u001b[39m\n\u001b[31m<!-- ── MODAL: AI GEO-FILL ── -->\u001b[39m\n\u001b[31m<div class=\\\"modal\\\" id=\\\"modal-geofill\\\">\u001b[39m\n\u001b[31m  <div class=\\\"modal-box\\\" style=\\\"max-height:90vh;display:flex;flex-direction:column\\\">\u001b[39m\n\u001b[31m    <div class=\\\"modal-hdr\\\">\u001b[39m\n\u001b[31m      <div class=\\\"modal-ttl\\\">✨ AI Geo-fill</div>\u001b[39m\n\u001b[31m      <button class=\\\"modal-close\\\" onclick=\\\"closeModal('modal-geofill')\\\">×</button>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Manual lookup panel (human in loop) -->\u001b[39m\n\u001b[31m    <div style=\\\"background:var(--card2);border:1px solid var(--border);border-radius:8px;padding:10px;margin-bottom:10px\\\">\u001b[39m\n\u001b[31m      <div style=\\\"font-size:11px;font-weight:700;color:var(--muted);margin-bottom:6px;text-transform:uppercase;letter-spacing:.4px\\\">🤖 Ask AI for any counter</div>\u001b[39m\n\u001b[31m      <div style=\\\"display:flex;gap:6px;flex-wrap:wrap;align-items:flex-end\\\">\u001b[39m\n\u001b[31m        <div style=\\\"flex:2;min-width:120px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">Counter</div>\u001b[39m\n\u001b[31m          <select id=\\\"gf-counter-sel\\\" onchange=\\\"gfCounterPick()\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit\\\"></select>\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">City</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-city\\\" placeholder=\\\"city\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <div style=\\\"flex:1;min-width:80px\\\">\u001b[39m\n\u001b[31m          <div style=\\\"font-size:10px;color:var(--muted);margin-bottom:2px\\\">State</div>\u001b[39m\n\u001b[31m          <input id=\\\"gf-state\\\" placeholder=\\\"state\\\" style=\\\"width:100%;font-size:11px;padding:4px 6px;border:1px solid var(--border);border-radius:6px;background:var(--card);color:var(--text);font-family:inherit;box-sizing:border-box\\\">\u001b[39m\n\u001b[31m        </div>\u001b[39m\n\u001b[31m        <button class=\\\"btn btn-p\\\" id=\\\"btn-gf-ask\\\" onclick=\\\"gfAskAI()\\\" style=\\\"font-size:11px;padding:5px 14px;white-space:nowrap\\\">Ask AI</button>\u001b[39m\n\u001b[31m      </div>\u001b[39m\n\u001b[31m      <div id=\\\"gf-ask-status\\\" style=\\\"font-size:11px;min-height:16px;margin-top:6px;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    </div>·\u001b[39m\n\u001b[31m    <!-- Auto-scan results table -->\u001b[39m\n\u001b[31m    <div style=\\\"font-size:11px;color:var(--muted);margin-bottom:4px\\\">Review suggestions — uncheck any you disagree with</div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-table\\\" style=\\\"overflow-y:auto;flex:1\\\"></div>\u001b[39m\n\u001b[31m    <div id=\\\"geofill-status\\\" style=\\\"font-size:12px;min-height:18px;margin:8px 0;color:var(--muted)\\\"></div>\u001b[39m\n\u001b[31m    <div class=\\\"btn-row\\\" style=\\\"margin-top:4px\\\">\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-s\\\" onclick=\\\"closeModal('modal-geofill')\\\">Cancel</button>\u001b[39m\n\u001b[31m      <button class=\\\"btn btn-p\\\" id=\\\"btn-geofill-apply\\\" onclick=\\\"applyGeoFill()\\\" disabled>Apply selected changes</button>\u001b[39m\n\u001b[31m    </div>\u001b[39m\n\u001b[31m  </div>\u001b[39m\n\u001b[31m</div>·\u001b[39m\n\u001b[31m<script>\u001b[39m\n\u001b[31m/* ── ADR-143: Client and Brand come from the shared `master` schema ──────────────\u001b[39m\n\u001b[31m   Self-contained and additive: if the master cannot be reached the page keeps\u001b[39m\n\u001b[31m   whatever it already showed, so this is never worse than before it existed. ── */\u001b[39m\n\u001b[31m(function () {\u001b[39m\n\u001b[31m  const MRD = { 'Accept-Profile': 'master' };\u001b[39m\n\u001b[31m  const _esc = n => String(n == null ? '' : n).replace(/[&<>\\\"]/g,\u001b[39m\n\u001b[31m    c => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '\\\"': '&quot;' }[c]));\u001b[39m\n\u001b[31m  async function loadMaster() {\u001b[39m\n\u001b[31m    try {\u001b[39m\n\u001b[31m      const [rb, rc] = await Promise.all([\u001b[39m\n\u001b[31m        fetch('/db/brands?select=brand_code,name&is_active=eq.true&order=sort_order', { headers: MRD }),\u001b[39m\n\u001b[31m        fetch('/db/clients?select=name&is_active=eq.true&order=name', { headers: MRD }),\u001b[39m\n\u001b[31m      ]);\u001b[39m\n\u001b[31m      if (!rb.ok || !rc.ok) return null;\u001b[39m\n\u001b[31m      return { brands: await rb.json(), clients: await rc.json() };\u001b[39m\n\u001b[31m    } catch (_) { return null; }\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  async function apply() {\u001b[39m\n\u001b[31m    const m = await loadMaster();\u001b[39m\n\u001b[31m    if (!m) return;\u001b[39m\n\u001b[31m    window.MASTER_BRANDS = m.brands;\u001b[39m\n\u001b[31m    window.MASTER_CLIENTS = m.clients;\u001b[39m\n\u001b[31m        window.MASTER_BRAND_CODES = m.brands.map(b => b.brand_code).filter(Boolean);\u001b[39m\n\u001b[31m  }\u001b[39m\n\u001b[31m  if (document.readyState === 'loading') addEventListener('DOMContentLoaded', apply);\u001b[39m\n\u001b[31m  else apply();\u001b[39m\n\u001b[31m})();\u001b[39m\n\u001b[31m</script>\u001b[39m\n\u001b[31m</body>\u001b[39m\n\u001b[31m</html>\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  227 |     expect(html).toContain('add_comment_as_supervisor');\n  228 |     expect(html).toContain('Recce comment');\n> 229 |     expect(html).toContain('💬 Comments');\n      |                  ^\n  230 |   });\n  231 |\n  232 |   test('Phase 4.18 — Sessions modal references pending-invite admin RPC', () => {\n    at /var/www/360lm/tests/counters.spec.js:229:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:54:28.318Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-HTML-s-76e62-ments-modal-RPCs-referenced-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 18,
                        "line": 229
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-62a05ed3cc7c0d871a07",
              "file": "counters.spec.js",
              "line": 222,
              "column": 3
            },
            {
              "title": "Phase 4.18 — Sessions modal references pending-invite admin RPC",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1079,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 16,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:29.573Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-2e04e2b59404a6b07824",
              "file": "counters.spec.js",
              "line": 232,
              "column": 3
            },
            {
              "title": "Phase 4.0a — signed-URL viewer wired (no direct window.open on view_url)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1079,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:29.765Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-ec0a5c31baff4db6c724",
              "file": "counters.spec.js",
              "line": 240,
              "column": 3
            }
          ]
        },
        {
          "title": "Counters — Auth",
          "file": "counters.spec.js",
          "line": 250,
          "column": 6,
          "specs": [
            {
              "title": "no session → redirects to /hub/?next=/counters/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 72,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 604,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:47.426Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-1680cfb700ea52756d0d",
              "file": "counters.spec.js",
              "line": 251,
              "column": 3
            },
            {
              "title": "supervisor (harish) sees + New button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 72,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1683,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:48.175Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-5874264e4b9e56a0e339",
              "file": "counters.spec.js",
              "line": 260,
              "column": 3
            },
            {
              "title": "non-supervisor (mukesh) hides + New + Edit",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 72,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2926,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:49.887Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-78a6126ea75f3f03e3bd",
              "file": "counters.spec.js",
              "line": 268,
              "column": 3
            },
            {
              "title": "rakesh is a supervisor",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 72,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1260,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:52.837Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-4a7b741f68d283a8622b",
              "file": "counters.spec.js",
              "line": 276,
              "column": 3
            },
            {
              "title": "no session → redirects to /hub/?next=/counters/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1079,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 595,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:29.783Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-ec4296c7343206cd41b7",
              "file": "counters.spec.js",
              "line": 251,
              "column": 3
            },
            {
              "title": "supervisor (harish) sees + New button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1079,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1288,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:30.522Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-62f7b24a5d82a4b8cac2",
              "file": "counters.spec.js",
              "line": 260,
              "column": 3
            },
            {
              "title": "non-supervisor (mukesh) hides + New + Edit",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1079,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1514,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:31.842Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-910871613892ae9c67f6",
              "file": "counters.spec.js",
              "line": 268,
              "column": 3
            },
            {
              "title": "rakesh is a supervisor",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1079,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1085,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:33.384Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-6040ac619f9e363326ad",
              "file": "counters.spec.js",
              "line": 276,
              "column": 3
            }
          ]
        },
        {
          "title": "Counters — Hub registration",
          "file": "counters.spec.js",
          "line": 288,
          "column": 6,
          "specs": [
            {
              "title": "PWA row in hub.pwa_registry with grants for harish, pramod, rakesh",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 72,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 29,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:54.117Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-9e8ddc0dbdde0aeab25f",
              "file": "counters.spec.js",
              "line": 289,
              "column": 3
            },
            {
              "title": "PWA row in hub.pwa_registry with grants for harish, pramod, rakesh",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1079,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 20,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:34.491Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-420b53a3ce9627ad3719",
              "file": "counters.spec.js",
              "line": 289,
              "column": 3
            }
          ]
        },
        {
          "title": "Counters — E2E supervisor flow",
          "file": "counters.spec.js",
          "line": 312,
          "column": 6,
          "specs": [
            {
              "title": "create counter → detail → add contact → search → cleanup",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 72,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11105,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 31782i\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 31782i\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n\n    at /var/www/360lm/tests/counters.spec.js:361:45",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 45,
                          "line": 361
                        },
                        "snippet": "  359 |     await page.fill('#srch', stamp);\n  360 |     await page.waitForTimeout(300);\n> 361 |     await expect(page.locator('#list-out')).toContainText(name);\n      |                                             ^\n  362 |\n  363 |     // Cleanup\n  364 |     if (counterId) {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 45,
                            "line": 361
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 31782i\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n\n\n  359 |     await page.fill('#srch', stamp);\n  360 |     await page.waitForTimeout(300);\n> 361 |     await expect(page.locator('#list-out')).toContainText(name);\n      |                                             ^\n  362 |\n  363 |     // Cleanup\n  364 |     if (counterId) {\n    at /var/www/360lm/tests/counters.spec.js:361:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:54:54.160Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a926d--contact-→-search-→-cleanup-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a926d--contact-→-search-→-cleanup-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 45,
                        "line": 361
                      }
                    },
                    {
                      "workerIndex": 73,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11466,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 317i6m\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 317i6m\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n\n    at /var/www/360lm/tests/counters.spec.js:361:45",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 45,
                          "line": 361
                        },
                        "snippet": "  359 |     await page.fill('#srch', stamp);\n  360 |     await page.waitForTimeout(300);\n> 361 |     await expect(page.locator('#list-out')).toContainText(name);\n      |                                             ^\n  362 |\n  363 |     // Cleanup\n  364 |     if (counterId) {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 45,
                            "line": 361
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 317i6m\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n\n\n  359 |     await page.fill('#srch', stamp);\n  360 |     await page.waitForTimeout(300);\n> 361 |     await expect(page.locator('#list-out')).toContainText(name);\n      |                                             ^\n  362 |\n  363 |     // Cleanup\n  364 |     if (counterId) {\n    at /var/www/360lm/tests/counters.spec.js:361:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:55:06.612Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a926d--contact-→-search-→-cleanup-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a926d--contact-→-search-→-cleanup-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 45,
                        "line": 361
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-80de57211557a60b2d4d",
              "file": "counters.spec.js",
              "line": 313,
              "column": 3
            },
            {
              "title": "Phase 2 — allocation modal allocates to All Employees",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 74,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4933,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:55:19.387Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-433dc713f35c576dcf66",
              "file": "counters.spec.js",
              "line": 373,
              "column": 3
            },
            {
              "title": "Phase 2 — upload approval creates counters + auto-allocates uploader",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 74,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4760,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:55:24.483Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-31d7f86e4eaf3ffce5b7",
              "file": "counters.spec.js",
              "line": 432,
              "column": 3
            },
            {
              "title": "Phase 3 — multi-Recce group: marking official demotes prior + trigger holds uniqueness",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 74,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6579,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:55:29.277Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-56569b6ae4348556d577",
              "file": "counters.spec.js",
              "line": 487,
              "column": 3
            },
            {
              "title": "Phase 4.5 — admin RPCs gate non-supervisors + full create-invite-revoke chain",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 74,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 68,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:602:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 602
                        },
                        "snippet": "  600 |       }),\n  601 |     })).json();\n> 602 |     expect(inv.ok).toBe(true);\n      |                    ^\n  603 |     const cuId = inv.client_user_id;\n  604 |     const inviteId = inv.invite_id;\n  605 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 602
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  600 |       }),\n  601 |     })).json();\n> 602 |     expect(inv.ok).toBe(true);\n      |                    ^\n  603 |     const cuId = inv.client_user_id;\n  604 |     const inviteId = inv.invite_id;\n  605 |\n    at /var/www/360lm/tests/counters.spec.js:602:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:55:35.885Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a326a--create-invite-revoke-chain-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 602
                      }
                    },
                    {
                      "workerIndex": 75,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 126,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:602:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 602
                        },
                        "snippet": "  600 |       }),\n  601 |     })).json();\n> 602 |     expect(inv.ok).toBe(true);\n      |                    ^\n  603 |     const cuId = inv.client_user_id;\n  604 |     const inviteId = inv.invite_id;\n  605 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 602
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  600 |       }),\n  601 |     })).json();\n> 602 |     expect(inv.ok).toBe(true);\n      |                    ^\n  603 |     const cuId = inv.client_user_id;\n  604 |     const inviteId = inv.invite_id;\n  605 |\n    at /var/www/360lm/tests/counters.spec.js:602:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:55:36.880Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a326a--create-invite-revoke-chain-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 602
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-aa84032f3d8124ba9ff5",
              "file": "counters.spec.js",
              "line": 566,
              "column": 3
            },
            {
              "title": "Phase 4.0a — legacy public path blocked + signed proxy URL serves the same file",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 76,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 71,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:687:29",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 29,
                          "line": 687
                        },
                        "snippet": "  685 |       'https://dev.srv1111289.hstgr.cloud/screenshots/recce/views/RC_1779435019915_0SZC.html'\n  686 |     );\n> 687 |     expect(legacy.status()).toBe(401);\n      |                             ^\n  688 |\n  689 |     // 2. /sign with Hub session returns a signed URL\n  690 |     const hubAuth = 'Hub ' + Buffer.from(JSON.stringify({"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 29,
                            "line": 687
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m\n\n  685 |       'https://dev.srv1111289.hstgr.cloud/screenshots/recce/views/RC_1779435019915_0SZC.html'\n  686 |     );\n> 687 |     expect(legacy.status()).toBe(401);\n      |                             ^\n  688 |\n  689 |     // 2. /sign with Hub session returns a signed URL\n  690 |     const hubAuth = 'Hub ' + Buffer.from(JSON.stringify({\n    at /var/www/360lm/tests/counters.spec.js:687:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:55:37.941Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-b644a-xy-URL-serves-the-same-file-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 29,
                        "line": 687
                      }
                    },
                    {
                      "workerIndex": 77,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:687:29",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 29,
                          "line": 687
                        },
                        "snippet": "  685 |       'https://dev.srv1111289.hstgr.cloud/screenshots/recce/views/RC_1779435019915_0SZC.html'\n  686 |     );\n> 687 |     expect(legacy.status()).toBe(401);\n      |                             ^\n  688 |\n  689 |     // 2. /sign with Hub session returns a signed URL\n  690 |     const hubAuth = 'Hub ' + Buffer.from(JSON.stringify({"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 29,
                            "line": 687
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m\n\n  685 |       'https://dev.srv1111289.hstgr.cloud/screenshots/recce/views/RC_1779435019915_0SZC.html'\n  686 |     );\n> 687 |     expect(legacy.status()).toBe(401);\n      |                             ^\n  688 |\n  689 |     // 2. /sign with Hub session returns a signed URL\n  690 |     const hubAuth = 'Hub ' + Buffer.from(JSON.stringify({\n    at /var/www/360lm/tests/counters.spec.js:687:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:55:38.872Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-b644a-xy-URL-serves-the-same-file-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 29,
                        "line": 687
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-522ec8a0692561b59110",
              "file": "counters.spec.js",
              "line": 681,
              "column": 3
            },
            {
              "title": "dedup panel surfaces for similar-name same-pincode entry",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 78,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7094,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n\n    at /var/www/360lm/tests/counters.spec.js:753:42",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 42,
                          "line": 753
                        },
                        "snippet": "  751 |     await page.fill('#f-pincode',      '160055');\n  752 |     await page.waitForTimeout(500);   // dedup is debounced 250ms\n> 753 |     await expect(page.locator('#dedup')).toBeVisible();\n      |                                          ^\n  754 |     await expect(page.locator('#dedup-list')).toContainText(seedName);\n  755 |\n  756 |     // Cleanup"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 42,
                            "line": 753
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n\n\n  751 |     await page.fill('#f-pincode',      '160055');\n  752 |     await page.waitForTimeout(500);   // dedup is debounced 250ms\n> 753 |     await expect(page.locator('#dedup')).toBeVisible();\n      |                                          ^\n  754 |     await expect(page.locator('#dedup-list')).toContainText(seedName);\n  755 |\n  756 |     // Cleanup\n    at /var/www/360lm/tests/counters.spec.js:753:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:55:39.870Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2a39f-lar-name-same-pincode-entry-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2a39f-lar-name-same-pincode-entry-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 42,
                        "line": 753
                      }
                    },
                    {
                      "workerIndex": 79,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7067,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n\n    at /var/www/360lm/tests/counters.spec.js:753:42",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 42,
                          "line": 753
                        },
                        "snippet": "  751 |     await page.fill('#f-pincode',      '160055');\n  752 |     await page.waitForTimeout(500);   // dedup is debounced 250ms\n> 753 |     await expect(page.locator('#dedup')).toBeVisible();\n      |                                          ^\n  754 |     await expect(page.locator('#dedup-list')).toContainText(seedName);\n  755 |\n  756 |     // Cleanup"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 42,
                            "line": 753
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n\n\n  751 |     await page.fill('#f-pincode',      '160055');\n  752 |     await page.waitForTimeout(500);   // dedup is debounced 250ms\n> 753 |     await expect(page.locator('#dedup')).toBeVisible();\n      |                                          ^\n  754 |     await expect(page.locator('#dedup-list')).toContainText(seedName);\n  755 |\n  756 |     // Cleanup\n    at /var/www/360lm/tests/counters.spec.js:753:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:55:48.025Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2a39f-lar-name-same-pincode-entry-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2a39f-lar-name-same-pincode-entry-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 42,
                        "line": 753
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-f5ea729591eb6877d065",
              "file": "counters.spec.js",
              "line": 720,
              "column": 3
            },
            {
              "title": "Phase 4.6 — official trigger fans out + dispatch_notify → mail_outbox + supervisor RPCs",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 80,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 86,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:794:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 794
                        },
                        "snippet": "  792 |       }),\n  793 |     })).json();\n> 794 |     expect(inv.ok).toBe(true);\n      |                    ^\n  795 |     const cuId     = inv.client_user_id;\n  796 |     const inviteId = inv.invite_id;\n  797 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 794
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  792 |       }),\n  793 |     })).json();\n> 794 |     expect(inv.ok).toBe(true);\n      |                    ^\n  795 |     const cuId     = inv.client_user_id;\n  796 |     const inviteId = inv.invite_id;\n  797 |\n    at /var/www/360lm/tests/counters.spec.js:794:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:55:56.190Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a9fa9-mail-outbox-supervisor-RPCs-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 794
                      }
                    },
                    {
                      "workerIndex": 81,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 95,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:794:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 794
                        },
                        "snippet": "  792 |       }),\n  793 |     })).json();\n> 794 |     expect(inv.ok).toBe(true);\n      |                    ^\n  795 |     const cuId     = inv.client_user_id;\n  796 |     const inviteId = inv.invite_id;\n  797 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 794
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  792 |       }),\n  793 |     })).json();\n> 794 |     expect(inv.ok).toBe(true);\n      |                    ^\n  795 |     const cuId     = inv.client_user_id;\n  796 |     const inviteId = inv.invite_id;\n  797 |\n    at /var/www/360lm/tests/counters.spec.js:794:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:55:57.170Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a9fa9-mail-outbox-supervisor-RPCs-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 794
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-601de3adbc07b4764052",
              "file": "counters.spec.js",
              "line": 761,
              "column": 3
            },
            {
              "title": "Phase 4.7 — audit log: consume_invite logs login, client_log_view logs view, client_set_response logs response, supervisor RPC gated + filterable",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 82,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 86,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:935:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 935
                        },
                        "snippet": "  933 |       }),\n  934 |     })).json();\n> 935 |     expect(inv.ok).toBe(true);\n      |                    ^\n  936 |     const cuId     = inv.client_user_id;\n  937 |     const inviteId = inv.invite_id;\n  938 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 935
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  933 |       }),\n  934 |     })).json();\n> 935 |     expect(inv.ok).toBe(true);\n      |                    ^\n  936 |     const cuId     = inv.client_user_id;\n  937 |     const inviteId = inv.invite_id;\n  938 |\n    at /var/www/360lm/tests/counters.spec.js:935:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:55:58.090Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-e1516-rvisor-RPC-gated-filterable-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 935
                      }
                    },
                    {
                      "workerIndex": 83,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 91,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:935:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 935
                        },
                        "snippet": "  933 |       }),\n  934 |     })).json();\n> 935 |     expect(inv.ok).toBe(true);\n      |                    ^\n  936 |     const cuId     = inv.client_user_id;\n  937 |     const inviteId = inv.invite_id;\n  938 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 935
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  933 |       }),\n  934 |     })).json();\n> 935 |     expect(inv.ok).toBe(true);\n      |                    ^\n  936 |     const cuId     = inv.client_user_id;\n  937 |     const inviteId = inv.invite_id;\n  938 |\n    at /var/www/360lm/tests/counters.spec.js:935:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:55:59.028Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-e1516-rvisor-RPC-gated-filterable-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 935
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-78a243c48295ecfd6c36",
              "file": "counters.spec.js",
              "line": 904,
              "column": 3
            },
            {
              "title": "Phase 4.8 — sessions: list_my_sessions / revoke_my_session / revoke_all_other_sessions / admin_list_sessions / admin_revoke_session",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 84,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 112,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'slice')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'slice')\n    at /var/www/360lm/tests/counters.spec.js:1065:90",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 90,
                          "line": 1065
                        },
                        "snippet": "  1063 |     for (const id of inviteIds) {\n  1064 |       const c = await (await ctx.post(`${DB}/rpc/consume_invite`, { headers: HC,\n> 1065 |         data: JSON.stringify({ p_invite_id: id, p_ip: '10.0.0.1', p_user_agent: `PW-${id.slice(-4)}` }),\n       |                                                                                          ^\n  1066 |       })).json();\n  1067 |       expect(c.ok).toBe(true);\n  1068 |       sessions.push(c.session_id);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 90,
                            "line": 1065
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'slice')\n\n  1063 |     for (const id of inviteIds) {\n  1064 |       const c = await (await ctx.post(`${DB}/rpc/consume_invite`, { headers: HC,\n> 1065 |         data: JSON.stringify({ p_invite_id: id, p_ip: '10.0.0.1', p_user_agent: `PW-${id.slice(-4)}` }),\n       |                                                                                          ^\n  1066 |       })).json();\n  1067 |       expect(c.ok).toBe(true);\n  1068 |       sessions.push(c.session_id);\n    at /var/www/360lm/tests/counters.spec.js:1065:90"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:56:00.001Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2d778-ssions-admin-revoke-session-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 90,
                        "line": 1065
                      }
                    },
                    {
                      "workerIndex": 85,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 174,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'slice')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'slice')\n    at /var/www/360lm/tests/counters.spec.js:1065:90",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 90,
                          "line": 1065
                        },
                        "snippet": "  1063 |     for (const id of inviteIds) {\n  1064 |       const c = await (await ctx.post(`${DB}/rpc/consume_invite`, { headers: HC,\n> 1065 |         data: JSON.stringify({ p_invite_id: id, p_ip: '10.0.0.1', p_user_agent: `PW-${id.slice(-4)}` }),\n       |                                                                                          ^\n  1066 |       })).json();\n  1067 |       expect(c.ok).toBe(true);\n  1068 |       sessions.push(c.session_id);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 90,
                            "line": 1065
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'slice')\n\n  1063 |     for (const id of inviteIds) {\n  1064 |       const c = await (await ctx.post(`${DB}/rpc/consume_invite`, { headers: HC,\n> 1065 |         data: JSON.stringify({ p_invite_id: id, p_ip: '10.0.0.1', p_user_agent: `PW-${id.slice(-4)}` }),\n       |                                                                                          ^\n  1066 |       })).json();\n  1067 |       expect(c.ok).toBe(true);\n  1068 |       sessions.push(c.session_id);\n    at /var/www/360lm/tests/counters.spec.js:1065:90"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:56:01.457Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2d778-ssions-admin-revoke-session-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 90,
                        "line": 1065
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-4cacf5432c3db03d54aa",
              "file": "counters.spec.js",
              "line": 1026,
              "column": 3
            },
            {
              "title": "Phase 4.12 — admin_list_audit date range + prune RPCs gating + count",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 86,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 174,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:1260:31",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 31,
                          "line": 1260
                        },
                        "snippet": "  1258 |       }),\n  1259 |     })).json();\n> 1260 |     expect(recentRows.length).toBeGreaterThanOrEqual(2);\n       |                               ^\n  1261 |\n  1262 |     // 3. admin_list_audit with p_to in the past → no rows\n  1263 |     const past = new Date(Date.now() - 24 * 3600 * 1000).toISOString();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 31,
                            "line": 1260
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  1258 |       }),\n  1259 |     })).json();\n> 1260 |     expect(recentRows.length).toBeGreaterThanOrEqual(2);\n       |                               ^\n  1261 |\n  1262 |     // 3. admin_list_audit with p_to in the past → no rows\n  1263 |     const past = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\n    at /var/www/360lm/tests/counters.spec.js:1260:31"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:56:03.175Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-b8e7e-nge-prune-RPCs-gating-count-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 31,
                        "line": 1260
                      }
                    },
                    {
                      "workerIndex": 87,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 166,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:1260:31",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 31,
                          "line": 1260
                        },
                        "snippet": "  1258 |       }),\n  1259 |     })).json();\n> 1260 |     expect(recentRows.length).toBeGreaterThanOrEqual(2);\n       |                               ^\n  1261 |\n  1262 |     // 3. admin_list_audit with p_to in the past → no rows\n  1263 |     const past = new Date(Date.now() - 24 * 3600 * 1000).toISOString();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 31,
                            "line": 1260
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  1258 |       }),\n  1259 |     })).json();\n> 1260 |     expect(recentRows.length).toBeGreaterThanOrEqual(2);\n       |                               ^\n  1261 |\n  1262 |     // 3. admin_list_audit with p_to in the past → no rows\n  1263 |     const past = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\n    at /var/www/360lm/tests/counters.spec.js:1260:31"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:56:04.523Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-b8e7e-nge-prune-RPCs-gating-count-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 31,
                        "line": 1260
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-30df780e8cb66eb0c406",
              "file": "counters.spec.js",
              "line": 1205,
              "column": 3
            },
            {
              "title": "Phase 4.14 — admin_audit_stats: shape + supervisor gate + counts match seeded events",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 88,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 250,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:1406:36",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 36,
                          "line": 1406
                        },
                        "snippet": "  1404 |     expect(s.window.to).toBeTruthy();\n  1405 |     // Logins: at least 1 in window, with the low chip from first_login\n> 1406 |     expect(s.logins_by_risk.total).toBeGreaterThanOrEqual(1);\n       |                                    ^\n  1407 |     expect(s.logins_by_risk.low).toBeGreaterThanOrEqual(1);\n  1408 |     // Top users contains our user with 2 views + 2 responses\n  1409 |     const me = s.top_users.find(u => u.client_user_id === cuId);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 36,
                            "line": 1406
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  1404 |     expect(s.window.to).toBeTruthy();\n  1405 |     // Logins: at least 1 in window, with the low chip from first_login\n> 1406 |     expect(s.logins_by_risk.total).toBeGreaterThanOrEqual(1);\n       |                                    ^\n  1407 |     expect(s.logins_by_risk.low).toBeGreaterThanOrEqual(1);\n  1408 |     // Top users contains our user with 2 views + 2 responses\n  1409 |     const me = s.top_users.find(u => u.client_user_id === cuId);\n    at /var/www/360lm/tests/counters.spec.js:1406:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:56:05.753Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-337d9--counts-match-seeded-events-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 36,
                        "line": 1406
                      }
                    },
                    {
                      "workerIndex": 89,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 201,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:1406:36",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 36,
                          "line": 1406
                        },
                        "snippet": "  1404 |     expect(s.window.to).toBeTruthy();\n  1405 |     // Logins: at least 1 in window, with the low chip from first_login\n> 1406 |     expect(s.logins_by_risk.total).toBeGreaterThanOrEqual(1);\n       |                                    ^\n  1407 |     expect(s.logins_by_risk.low).toBeGreaterThanOrEqual(1);\n  1408 |     // Top users contains our user with 2 views + 2 responses\n  1409 |     const me = s.top_users.find(u => u.client_user_id === cuId);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 36,
                            "line": 1406
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  1404 |     expect(s.window.to).toBeTruthy();\n  1405 |     // Logins: at least 1 in window, with the low chip from first_login\n> 1406 |     expect(s.logins_by_risk.total).toBeGreaterThanOrEqual(1);\n       |                                    ^\n  1407 |     expect(s.logins_by_risk.low).toBeGreaterThanOrEqual(1);\n  1408 |     // Top users contains our user with 2 views + 2 responses\n  1409 |     const me = s.top_users.find(u => u.client_user_id === cuId);\n    at /var/www/360lm/tests/counters.spec.js:1406:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:56:06.942Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-337d9--counts-match-seeded-events-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 36,
                        "line": 1406
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-78a15a8634e91391cccc",
              "file": "counters.spec.js",
              "line": 1315,
              "column": 3
            },
            {
              "title": "Phase 4.15 — supervisor_weekly_digest_for: unknown rejected; known sends mail + notify row; _all returns aggregate shape",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 90,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 170,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:56:08.049Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-8dd72ef87e46ae4fa919",
              "file": "counters.spec.js",
              "line": 1440,
              "column": 3
            },
            {
              "title": "Phase 4.17 — recce comments: both directions + visibility gate + trigger fan-out",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 90,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n    at /var/www/360lm/tests/counters.spec.js:1548:21",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 21,
                          "line": 1548
                        },
                        "snippet": "  1546 |       data: JSON.stringify({ p_session_id: sessionId, p_sub_id: subId, p_body: '   ' }),\n  1547 |     })).json();\n> 1548 |     expect(badBody).toEqual({ ok: false, error: 'body_required' });\n       |                     ^\n  1549 |\n  1550 |     // 2. Validation: bad session\n  1551 |     const badSess = await (await ctx.post(`${DB}/rpc/add_comment_as_client`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 21,
                            "line": 1548
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n\n  1546 |       data: JSON.stringify({ p_session_id: sessionId, p_sub_id: subId, p_body: '   ' }),\n  1547 |     })).json();\n> 1548 |     expect(badBody).toEqual({ ok: false, error: 'body_required' });\n       |                     ^\n  1549 |\n  1550 |     // 2. Validation: bad session\n  1551 |     const badSess = await (await ctx.post(`${DB}/rpc/add_comment_as_client`, { headers: HC,\n    at /var/www/360lm/tests/counters.spec.js:1548:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:56:08.277Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-21c8e-bility-gate-trigger-fan-out-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 21,
                        "line": 1548
                      }
                    },
                    {
                      "workerIndex": 91,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 114,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n    at /var/www/360lm/tests/counters.spec.js:1548:21",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 21,
                          "line": 1548
                        },
                        "snippet": "  1546 |       data: JSON.stringify({ p_session_id: sessionId, p_sub_id: subId, p_body: '   ' }),\n  1547 |     })).json();\n> 1548 |     expect(badBody).toEqual({ ok: false, error: 'body_required' });\n       |                     ^\n  1549 |\n  1550 |     // 2. Validation: bad session\n  1551 |     const badSess = await (await ctx.post(`${DB}/rpc/add_comment_as_client`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 21,
                            "line": 1548
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n\n  1546 |       data: JSON.stringify({ p_session_id: sessionId, p_sub_id: subId, p_body: '   ' }),\n  1547 |     })).json();\n> 1548 |     expect(badBody).toEqual({ ok: false, error: 'body_required' });\n       |                     ^\n  1549 |\n  1550 |     // 2. Validation: bad session\n  1551 |     const badSess = await (await ctx.post(`${DB}/rpc/add_comment_as_client`, { headers: HC,\n    at /var/www/360lm/tests/counters.spec.js:1548:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:56:09.178Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-21c8e-bility-gate-trigger-fan-out-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 21,
                        "line": 1548
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-9938197b27a3e3d89140",
              "file": "counters.spec.js",
              "line": 1505,
              "column": 3
            },
            {
              "title": "create counter → detail → add contact → search → cleanup",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1079,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11122,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 37ma8t\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 37ma8t\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n\n    at /var/www/360lm/tests/counters.spec.js:361:45",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 45,
                          "line": 361
                        },
                        "snippet": "  359 |     await page.fill('#srch', stamp);\n  360 |     await page.waitForTimeout(300);\n> 361 |     await expect(page.locator('#list-out')).toContainText(name);\n      |                                             ^\n  362 |\n  363 |     // Cleanup\n  364 |     if (counterId) {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 45,
                            "line": 361
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 37ma8t\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n\n\n  359 |     await page.fill('#srch', stamp);\n  360 |     await page.waitForTimeout(300);\n> 361 |     await expect(page.locator('#list-out')).toContainText(name);\n      |                                             ^\n  362 |\n  363 |     // Cleanup\n  364 |     if (counterId) {\n    at /var/www/360lm/tests/counters.spec.js:361:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:34.527Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a926d--contact-→-search-→-cleanup-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a926d--contact-→-search-→-cleanup-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 45,
                        "line": 361
                      }
                    },
                    {
                      "workerIndex": 1080,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11359,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 37mjss\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 37mjss\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n\n    at /var/www/360lm/tests/counters.spec.js:361:45",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 45,
                          "line": 361
                        },
                        "snippet": "  359 |     await page.fill('#srch', stamp);\n  360 |     await page.waitForTimeout(300);\n> 361 |     await expect(page.locator('#list-out')).toContainText(name);\n      |                                             ^\n  362 |\n  363 |     // Cleanup\n  364 |     if (counterId) {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 45,
                            "line": 361
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#list-out')\nExpected substring: \u001b[32m\"Playwright Counter 37mjss\"\u001b[39m\nReceived string:    \u001b[31m\"🏪No counters yet. Tap + New to add one.\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#list-out')\u001b[22m\n\u001b[2m    14 × locator resolved to <div id=\"list-out\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"🏪No counters yet. Tap + New to add one.\"\u001b[22m\n\n\n  359 |     await page.fill('#srch', stamp);\n  360 |     await page.waitForTimeout(300);\n> 361 |     await expect(page.locator('#list-out')).toContainText(name);\n      |                                             ^\n  362 |\n  363 |     // Cleanup\n  364 |     if (counterId) {\n    at /var/www/360lm/tests/counters.spec.js:361:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:54:46.593Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a926d--contact-→-search-→-cleanup-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a926d--contact-→-search-→-cleanup-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 45,
                        "line": 361
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-b512e9a8678ef318cf8a",
              "file": "counters.spec.js",
              "line": 313,
              "column": 3
            },
            {
              "title": "Phase 2 — allocation modal allocates to All Employees",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1081,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5533,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:54:59.025Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-38135d69a3d4418aaf58",
              "file": "counters.spec.js",
              "line": 373,
              "column": 3
            },
            {
              "title": "Phase 2 — upload approval creates counters + auto-allocates uploader",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1081,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5075,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:04.765Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-17e308923eaa4a13fac5",
              "file": "counters.spec.js",
              "line": 432,
              "column": 3
            },
            {
              "title": "Phase 3 — multi-Recce group: marking official demotes prior + trigger holds uniqueness",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1081,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6549,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:09.861Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-79c899ab85e1bf616435",
              "file": "counters.spec.js",
              "line": 487,
              "column": 3
            },
            {
              "title": "Phase 4.5 — admin RPCs gate non-supervisors + full create-invite-revoke chain",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1081,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 45,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:602:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 602
                        },
                        "snippet": "  600 |       }),\n  601 |     })).json();\n> 602 |     expect(inv.ok).toBe(true);\n      |                    ^\n  603 |     const cuId = inv.client_user_id;\n  604 |     const inviteId = inv.invite_id;\n  605 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 602
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  600 |       }),\n  601 |     })).json();\n> 602 |     expect(inv.ok).toBe(true);\n      |                    ^\n  603 |     const cuId = inv.client_user_id;\n  604 |     const inviteId = inv.invite_id;\n  605 |\n    at /var/www/360lm/tests/counters.spec.js:602:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:16.427Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a326a--create-invite-revoke-chain-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 602
                      }
                    },
                    {
                      "workerIndex": 1082,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 111,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:602:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 602
                        },
                        "snippet": "  600 |       }),\n  601 |     })).json();\n> 602 |     expect(inv.ok).toBe(true);\n      |                    ^\n  603 |     const cuId = inv.client_user_id;\n  604 |     const inviteId = inv.invite_id;\n  605 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 602
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  600 |       }),\n  601 |     })).json();\n> 602 |     expect(inv.ok).toBe(true);\n      |                    ^\n  603 |     const cuId = inv.client_user_id;\n  604 |     const inviteId = inv.invite_id;\n  605 |\n    at /var/www/360lm/tests/counters.spec.js:602:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:55:17.364Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a326a--create-invite-revoke-chain-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 602
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-445cd039cdbd7340a795",
              "file": "counters.spec.js",
              "line": 566,
              "column": 3
            },
            {
              "title": "Phase 4.0a — legacy public path blocked + signed proxy URL serves the same file",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1083,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:687:29",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 29,
                          "line": 687
                        },
                        "snippet": "  685 |       'https://dev.srv1111289.hstgr.cloud/screenshots/recce/views/RC_1779435019915_0SZC.html'\n  686 |     );\n> 687 |     expect(legacy.status()).toBe(401);\n      |                             ^\n  688 |\n  689 |     // 2. /sign with Hub session returns a signed URL\n  690 |     const hubAuth = 'Hub ' + Buffer.from(JSON.stringify({"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 29,
                            "line": 687
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m\n\n  685 |       'https://dev.srv1111289.hstgr.cloud/screenshots/recce/views/RC_1779435019915_0SZC.html'\n  686 |     );\n> 687 |     expect(legacy.status()).toBe(401);\n      |                             ^\n  688 |\n  689 |     // 2. /sign with Hub session returns a signed URL\n  690 |     const hubAuth = 'Hub ' + Buffer.from(JSON.stringify({\n    at /var/www/360lm/tests/counters.spec.js:687:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:18.410Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-b644a-xy-URL-serves-the-same-file-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 29,
                        "line": 687
                      }
                    },
                    {
                      "workerIndex": 1084,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:687:29",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 29,
                          "line": 687
                        },
                        "snippet": "  685 |       'https://dev.srv1111289.hstgr.cloud/screenshots/recce/views/RC_1779435019915_0SZC.html'\n  686 |     );\n> 687 |     expect(legacy.status()).toBe(401);\n      |                             ^\n  688 |\n  689 |     // 2. /sign with Hub session returns a signed URL\n  690 |     const hubAuth = 'Hub ' + Buffer.from(JSON.stringify({"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 29,
                            "line": 687
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m200\u001b[39m\n\n  685 |       'https://dev.srv1111289.hstgr.cloud/screenshots/recce/views/RC_1779435019915_0SZC.html'\n  686 |     );\n> 687 |     expect(legacy.status()).toBe(401);\n      |                             ^\n  688 |\n  689 |     // 2. /sign with Hub session returns a signed URL\n  690 |     const hubAuth = 'Hub ' + Buffer.from(JSON.stringify({\n    at /var/www/360lm/tests/counters.spec.js:687:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:55:19.341Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-b644a-xy-URL-serves-the-same-file-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 29,
                        "line": 687
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-1f9fb46e7dd12ef19818",
              "file": "counters.spec.js",
              "line": 681,
              "column": 3
            },
            {
              "title": "dedup panel surfaces for similar-name same-pincode entry",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1085,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7103,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n\n    at /var/www/360lm/tests/counters.spec.js:753:42",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 42,
                          "line": 753
                        },
                        "snippet": "  751 |     await page.fill('#f-pincode',      '160055');\n  752 |     await page.waitForTimeout(500);   // dedup is debounced 250ms\n> 753 |     await expect(page.locator('#dedup')).toBeVisible();\n      |                                          ^\n  754 |     await expect(page.locator('#dedup-list')).toContainText(seedName);\n  755 |\n  756 |     // Cleanup"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 42,
                            "line": 753
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n\n\n  751 |     await page.fill('#f-pincode',      '160055');\n  752 |     await page.waitForTimeout(500);   // dedup is debounced 250ms\n> 753 |     await expect(page.locator('#dedup')).toBeVisible();\n      |                                          ^\n  754 |     await expect(page.locator('#dedup-list')).toContainText(seedName);\n  755 |\n  756 |     // Cleanup\n    at /var/www/360lm/tests/counters.spec.js:753:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:20.315Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2a39f-lar-name-same-pincode-entry-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2a39f-lar-name-same-pincode-entry-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 42,
                        "line": 753
                      }
                    },
                    {
                      "workerIndex": 1086,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7039,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n\n    at /var/www/360lm/tests/counters.spec.js:753:42",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 42,
                          "line": 753
                        },
                        "snippet": "  751 |     await page.fill('#f-pincode',      '160055');\n  752 |     await page.waitForTimeout(500);   // dedup is debounced 250ms\n> 753 |     await expect(page.locator('#dedup')).toBeVisible();\n      |                                          ^\n  754 |     await expect(page.locator('#dedup-list')).toContainText(seedName);\n  755 |\n  756 |     // Cleanup"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 42,
                            "line": 753
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#dedup')\nExpected: visible\nReceived: hidden\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dedup')\u001b[22m\n\u001b[2m    13 × locator resolved to <div id=\"dedup\" class=\"dedup-panel\">…</div>\u001b[22m\n\u001b[2m       - unexpected value \"hidden\"\u001b[22m\n\n\n  751 |     await page.fill('#f-pincode',      '160055');\n  752 |     await page.waitForTimeout(500);   // dedup is debounced 250ms\n> 753 |     await expect(page.locator('#dedup')).toBeVisible();\n      |                                          ^\n  754 |     await expect(page.locator('#dedup-list')).toContainText(seedName);\n  755 |\n  756 |     // Cleanup\n    at /var/www/360lm/tests/counters.spec.js:753:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:55:28.576Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2a39f-lar-name-same-pincode-entry-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2a39f-lar-name-same-pincode-entry-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 42,
                        "line": 753
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-d96cbd2ded2ab89abb5b",
              "file": "counters.spec.js",
              "line": 720,
              "column": 3
            },
            {
              "title": "Phase 4.6 — official trigger fans out + dispatch_notify → mail_outbox + supervisor RPCs",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1087,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 88,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:794:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 794
                        },
                        "snippet": "  792 |       }),\n  793 |     })).json();\n> 794 |     expect(inv.ok).toBe(true);\n      |                    ^\n  795 |     const cuId     = inv.client_user_id;\n  796 |     const inviteId = inv.invite_id;\n  797 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 794
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  792 |       }),\n  793 |     })).json();\n> 794 |     expect(inv.ok).toBe(true);\n      |                    ^\n  795 |     const cuId     = inv.client_user_id;\n  796 |     const inviteId = inv.invite_id;\n  797 |\n    at /var/www/360lm/tests/counters.spec.js:794:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:36.710Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a9fa9-mail-outbox-supervisor-RPCs-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 794
                      }
                    },
                    {
                      "workerIndex": 1088,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 102,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:794:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 794
                        },
                        "snippet": "  792 |       }),\n  793 |     })).json();\n> 794 |     expect(inv.ok).toBe(true);\n      |                    ^\n  795 |     const cuId     = inv.client_user_id;\n  796 |     const inviteId = inv.invite_id;\n  797 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 794
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  792 |       }),\n  793 |     })).json();\n> 794 |     expect(inv.ok).toBe(true);\n      |                    ^\n  795 |     const cuId     = inv.client_user_id;\n  796 |     const inviteId = inv.invite_id;\n  797 |\n    at /var/www/360lm/tests/counters.spec.js:794:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:55:37.675Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-a9fa9-mail-outbox-supervisor-RPCs-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 794
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-ac504cf6c03ad7d570e3",
              "file": "counters.spec.js",
              "line": 761,
              "column": 3
            },
            {
              "title": "Phase 4.7 — audit log: consume_invite logs login, client_log_view logs view, client_set_response logs response, supervisor RPC gated + filterable",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1089,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 83,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:935:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 935
                        },
                        "snippet": "  933 |       }),\n  934 |     })).json();\n> 935 |     expect(inv.ok).toBe(true);\n      |                    ^\n  936 |     const cuId     = inv.client_user_id;\n  937 |     const inviteId = inv.invite_id;\n  938 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 935
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  933 |       }),\n  934 |     })).json();\n> 935 |     expect(inv.ok).toBe(true);\n      |                    ^\n  936 |     const cuId     = inv.client_user_id;\n  937 |     const inviteId = inv.invite_id;\n  938 |\n    at /var/www/360lm/tests/counters.spec.js:935:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:38.840Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-e1516-rvisor-RPC-gated-filterable-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 935
                      }
                    },
                    {
                      "workerIndex": 1090,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 85,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:935:20",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 20,
                          "line": 935
                        },
                        "snippet": "  933 |       }),\n  934 |     })).json();\n> 935 |     expect(inv.ok).toBe(true);\n      |                    ^\n  936 |     const cuId     = inv.client_user_id;\n  937 |     const inviteId = inv.invite_id;\n  938 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 20,
                            "line": 935
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  933 |       }),\n  934 |     })).json();\n> 935 |     expect(inv.ok).toBe(true);\n      |                    ^\n  936 |     const cuId     = inv.client_user_id;\n  937 |     const inviteId = inv.invite_id;\n  938 |\n    at /var/www/360lm/tests/counters.spec.js:935:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:55:39.876Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-e1516-rvisor-RPC-gated-filterable-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 20,
                        "line": 935
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-716ed74efa1a9a565a68",
              "file": "counters.spec.js",
              "line": 904,
              "column": 3
            },
            {
              "title": "Phase 4.8 — sessions: list_my_sessions / revoke_my_session / revoke_all_other_sessions / admin_list_sessions / admin_revoke_session",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1091,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 99,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'slice')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'slice')\n    at /var/www/360lm/tests/counters.spec.js:1065:90",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 90,
                          "line": 1065
                        },
                        "snippet": "  1063 |     for (const id of inviteIds) {\n  1064 |       const c = await (await ctx.post(`${DB}/rpc/consume_invite`, { headers: HC,\n> 1065 |         data: JSON.stringify({ p_invite_id: id, p_ip: '10.0.0.1', p_user_agent: `PW-${id.slice(-4)}` }),\n       |                                                                                          ^\n  1066 |       })).json();\n  1067 |       expect(c.ok).toBe(true);\n  1068 |       sessions.push(c.session_id);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 90,
                            "line": 1065
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'slice')\n\n  1063 |     for (const id of inviteIds) {\n  1064 |       const c = await (await ctx.post(`${DB}/rpc/consume_invite`, { headers: HC,\n> 1065 |         data: JSON.stringify({ p_invite_id: id, p_ip: '10.0.0.1', p_user_agent: `PW-${id.slice(-4)}` }),\n       |                                                                                          ^\n  1066 |       })).json();\n  1067 |       expect(c.ok).toBe(true);\n  1068 |       sessions.push(c.session_id);\n    at /var/www/360lm/tests/counters.spec.js:1065:90"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:40.883Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2d778-ssions-admin-revoke-session-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 90,
                        "line": 1065
                      }
                    },
                    {
                      "workerIndex": 1092,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 96,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'slice')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'slice')\n    at /var/www/360lm/tests/counters.spec.js:1065:90",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 90,
                          "line": 1065
                        },
                        "snippet": "  1063 |     for (const id of inviteIds) {\n  1064 |       const c = await (await ctx.post(`${DB}/rpc/consume_invite`, { headers: HC,\n> 1065 |         data: JSON.stringify({ p_invite_id: id, p_ip: '10.0.0.1', p_user_agent: `PW-${id.slice(-4)}` }),\n       |                                                                                          ^\n  1066 |       })).json();\n  1067 |       expect(c.ok).toBe(true);\n  1068 |       sessions.push(c.session_id);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 90,
                            "line": 1065
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'slice')\n\n  1063 |     for (const id of inviteIds) {\n  1064 |       const c = await (await ctx.post(`${DB}/rpc/consume_invite`, { headers: HC,\n> 1065 |         data: JSON.stringify({ p_invite_id: id, p_ip: '10.0.0.1', p_user_agent: `PW-${id.slice(-4)}` }),\n       |                                                                                          ^\n  1066 |       })).json();\n  1067 |       expect(c.ok).toBe(true);\n  1068 |       sessions.push(c.session_id);\n    at /var/www/360lm/tests/counters.spec.js:1065:90"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:55:41.944Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-2d778-ssions-admin-revoke-session-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 90,
                        "line": 1065
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-1dcca5cd9616d6e5ed3e",
              "file": "counters.spec.js",
              "line": 1026,
              "column": 3
            },
            {
              "title": "Phase 4.12 — admin_list_audit date range + prune RPCs gating + count",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1093,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 124,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:1260:31",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 31,
                          "line": 1260
                        },
                        "snippet": "  1258 |       }),\n  1259 |     })).json();\n> 1260 |     expect(recentRows.length).toBeGreaterThanOrEqual(2);\n       |                               ^\n  1261 |\n  1262 |     // 3. admin_list_audit with p_to in the past → no rows\n  1263 |     const past = new Date(Date.now() - 24 * 3600 * 1000).toISOString();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 31,
                            "line": 1260
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  1258 |       }),\n  1259 |     })).json();\n> 1260 |     expect(recentRows.length).toBeGreaterThanOrEqual(2);\n       |                               ^\n  1261 |\n  1262 |     // 3. admin_list_audit with p_to in the past → no rows\n  1263 |     const past = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\n    at /var/www/360lm/tests/counters.spec.js:1260:31"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:42.999Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-b8e7e-nge-prune-RPCs-gating-count-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 31,
                        "line": 1260
                      }
                    },
                    {
                      "workerIndex": 1094,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 130,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:1260:31",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 31,
                          "line": 1260
                        },
                        "snippet": "  1258 |       }),\n  1259 |     })).json();\n> 1260 |     expect(recentRows.length).toBeGreaterThanOrEqual(2);\n       |                               ^\n  1261 |\n  1262 |     // 3. admin_list_audit with p_to in the past → no rows\n  1263 |     const past = new Date(Date.now() - 24 * 3600 * 1000).toISOString();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 31,
                            "line": 1260
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  1258 |       }),\n  1259 |     })).json();\n> 1260 |     expect(recentRows.length).toBeGreaterThanOrEqual(2);\n       |                               ^\n  1261 |\n  1262 |     // 3. admin_list_audit with p_to in the past → no rows\n  1263 |     const past = new Date(Date.now() - 24 * 3600 * 1000).toISOString();\n    at /var/www/360lm/tests/counters.spec.js:1260:31"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:55:43.996Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-b8e7e-nge-prune-RPCs-gating-count-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 31,
                        "line": 1260
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-db8d141eb603f9a8329b",
              "file": "counters.spec.js",
              "line": 1205,
              "column": 3
            },
            {
              "title": "Phase 4.14 — admin_audit_stats: shape + supervisor gate + counts match seeded events",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1095,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 210,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:1406:36",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 36,
                          "line": 1406
                        },
                        "snippet": "  1404 |     expect(s.window.to).toBeTruthy();\n  1405 |     // Logins: at least 1 in window, with the low chip from first_login\n> 1406 |     expect(s.logins_by_risk.total).toBeGreaterThanOrEqual(1);\n       |                                    ^\n  1407 |     expect(s.logins_by_risk.low).toBeGreaterThanOrEqual(1);\n  1408 |     // Top users contains our user with 2 views + 2 responses\n  1409 |     const me = s.top_users.find(u => u.client_user_id === cuId);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 36,
                            "line": 1406
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  1404 |     expect(s.window.to).toBeTruthy();\n  1405 |     // Logins: at least 1 in window, with the low chip from first_login\n> 1406 |     expect(s.logins_by_risk.total).toBeGreaterThanOrEqual(1);\n       |                                    ^\n  1407 |     expect(s.logins_by_risk.low).toBeGreaterThanOrEqual(1);\n  1408 |     // Top users contains our user with 2 views + 2 responses\n  1409 |     const me = s.top_users.find(u => u.client_user_id === cuId);\n    at /var/www/360lm/tests/counters.spec.js:1406:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:45.009Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-337d9--counts-match-seeded-events-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 36,
                        "line": 1406
                      }
                    },
                    {
                      "workerIndex": 1096,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 201,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/counters.spec.js:1406:36",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 36,
                          "line": 1406
                        },
                        "snippet": "  1404 |     expect(s.window.to).toBeTruthy();\n  1405 |     // Logins: at least 1 in window, with the low chip from first_login\n> 1406 |     expect(s.logins_by_risk.total).toBeGreaterThanOrEqual(1);\n       |                                    ^\n  1407 |     expect(s.logins_by_risk.low).toBeGreaterThanOrEqual(1);\n  1408 |     // Top users contains our user with 2 views + 2 responses\n  1409 |     const me = s.top_users.find(u => u.client_user_id === cuId);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 36,
                            "line": 1406
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m1\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  1404 |     expect(s.window.to).toBeTruthy();\n  1405 |     // Logins: at least 1 in window, with the low chip from first_login\n> 1406 |     expect(s.logins_by_risk.total).toBeGreaterThanOrEqual(1);\n       |                                    ^\n  1407 |     expect(s.logins_by_risk.low).toBeGreaterThanOrEqual(1);\n  1408 |     // Top users contains our user with 2 views + 2 responses\n  1409 |     const me = s.top_users.find(u => u.client_user_id === cuId);\n    at /var/www/360lm/tests/counters.spec.js:1406:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:55:46.146Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-337d9--counts-match-seeded-events-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 36,
                        "line": 1406
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-459fe856ce53f2c1fbc5",
              "file": "counters.spec.js",
              "line": 1315,
              "column": 3
            },
            {
              "title": "Phase 4.15 — supervisor_weekly_digest_for: unknown rejected; known sends mail + notify row; _all returns aggregate shape",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1097,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 184,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:47.188Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "8dd817b7ae940868ac19-c5657dc4f8cf60947680",
              "file": "counters.spec.js",
              "line": 1440,
              "column": 3
            },
            {
              "title": "Phase 4.17 — recce comments: both directions + visibility gate + trigger fan-out",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1097,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 89,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n    at /var/www/360lm/tests/counters.spec.js:1548:21",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 21,
                          "line": 1548
                        },
                        "snippet": "  1546 |       data: JSON.stringify({ p_session_id: sessionId, p_sub_id: subId, p_body: '   ' }),\n  1547 |     })).json();\n> 1548 |     expect(badBody).toEqual({ ok: false, error: 'body_required' });\n       |                     ^\n  1549 |\n  1550 |     // 2. Validation: bad session\n  1551 |     const badSess = await (await ctx.post(`${DB}/rpc/add_comment_as_client`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 21,
                            "line": 1548
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n\n  1546 |       data: JSON.stringify({ p_session_id: sessionId, p_sub_id: subId, p_body: '   ' }),\n  1547 |     })).json();\n> 1548 |     expect(badBody).toEqual({ ok: false, error: 'body_required' });\n       |                     ^\n  1549 |\n  1550 |     // 2. Validation: bad session\n  1551 |     const badSess = await (await ctx.post(`${DB}/rpc/add_comment_as_client`, { headers: HC,\n    at /var/www/360lm/tests/counters.spec.js:1548:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:47.415Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-21c8e-bility-gate-trigger-fan-out-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 21,
                        "line": 1548
                      }
                    },
                    {
                      "workerIndex": 1098,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 109,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n    at /var/www/360lm/tests/counters.spec.js:1548:21",
                        "location": {
                          "file": "/var/www/360lm/tests/counters.spec.js",
                          "column": 21,
                          "line": 1548
                        },
                        "snippet": "  1546 |       data: JSON.stringify({ p_session_id: sessionId, p_sub_id: subId, p_body: '   ' }),\n  1547 |     })).json();\n> 1548 |     expect(badBody).toEqual({ ok: false, error: 'body_required' });\n       |                     ^\n  1549 |\n  1550 |     // 2. Validation: bad session\n  1551 |     const badSess = await (await ctx.post(`${DB}/rpc/add_comment_as_client`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/counters.spec.js",
                            "column": 21,
                            "line": 1548
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"body_required\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.add_comment_as_client with parameters p_body, p_sub_id or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.add_comment_as_client(p_body, p_session_id, p_sub_id)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.add_comment_as_client(p_body, p_sub_id) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n\n  1546 |       data: JSON.stringify({ p_session_id: sessionId, p_sub_id: subId, p_body: '   ' }),\n  1547 |     })).json();\n> 1548 |     expect(badBody).toEqual({ ok: false, error: 'body_required' });\n       |                     ^\n  1549 |\n  1550 |     // 2. Validation: bad session\n  1551 |     const badSess = await (await ctx.post(`${DB}/rpc/add_comment_as_client`, { headers: HC,\n    at /var/www/360lm/tests/counters.spec.js:1548:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:55:48.350Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/counters-Counters-—-E2E-su-21c8e-bility-gate-trigger-fan-out-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/counters.spec.js",
                        "column": 21,
                        "line": 1548
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "8dd817b7ae940868ac19-901277a5373f16671976",
              "file": "counters.spec.js",
              "line": 1505,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "custodian_voucher_tutorial.spec.js",
      "file": "custodian_voucher_tutorial.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Custodian Voucher Tutorial",
          "file": "custodian_voucher_tutorial.spec.js",
          "line": 88,
          "column": 6,
          "specs": [
            {
              "title": "25-voucher-intro",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:49.223Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-330682d158057110244c",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 97,
              "column": 3
            },
            {
              "title": "26-create-draft",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:49.223Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-9a73b9c40e230f2cddaf",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 105,
              "column": 3
            },
            {
              "title": "27-find-advance",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:49.223Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-87eeffc017a27d58f7d2",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 115,
              "column": 3
            },
            {
              "title": "28-credit-card-deduction",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:49.223Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-621fa6055e2e0e737329",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 134,
              "column": 3
            },
            {
              "title": "29-custodian-direct",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:49.223Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-3eb757c2f8d94912c42f",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 148,
              "column": 3
            },
            {
              "title": "30-new-advance",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:49.223Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-06de9eb8777407fbb061",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 160,
              "column": 3
            },
            {
              "title": "31-book-voucher",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:49.223Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-5c6f23bbecccaa166dcd",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 171,
              "column": 3
            },
            {
              "title": "32-settle-cancel",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:49.223Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-6859bef0fb3ccee4f90b",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 183,
              "column": 3
            },
            {
              "title": "25-voucher-intro",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:28.434Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-f82544f8e9573e0c89d9",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 97,
              "column": 3
            },
            {
              "title": "26-create-draft",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:28.434Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-a66f3b74f6178b8b4fc5",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 105,
              "column": 3
            },
            {
              "title": "27-find-advance",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:28.434Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-783dcf9442fc36d83bbf",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 115,
              "column": 3
            },
            {
              "title": "28-credit-card-deduction",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:28.434Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-c491fc21fcca444a9088",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 134,
              "column": 3
            },
            {
              "title": "29-custodian-direct",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:28.434Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-43cb9598c0a456df8629",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 148,
              "column": 3
            },
            {
              "title": "30-new-advance",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:28.434Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-eac32e64df33eb88b19e",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 160,
              "column": 3
            },
            {
              "title": "31-book-voucher",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:28.437Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-6a05d273a4cd2a54e3f4",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 171,
              "column": 3
            },
            {
              "title": "32-settle-cancel",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                      "location": {
                        "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                        "line": 86,
                        "column": 6
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:28.437Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "Voucher tutorial — set TUTORIAL_VOUCHER=1 to enable (requires dev fixtures, see file header)",
                          "location": {
                            "file": "/var/www/360lm/tests/custodian_voucher_tutorial.spec.js",
                            "line": 86,
                            "column": 6
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e840dfba0de86866c7bc-b4f057c8b59e8bb1a082",
              "file": "custodian_voucher_tutorial.spec.js",
              "line": 183,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "custodian.spec.js",
      "file": "custodian.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Fund Custodian — Access Control",
          "file": "custodian.spec.js",
          "line": 18,
          "column": 6,
          "specs": [
            {
              "title": "no session → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 92,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5272,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:21:24",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 24,
                          "line": 21
                        },
                        "snippet": "  19 |   test('no session → redirects to /hub/', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page).toHaveURL(/\\/hub\\//);\n     |                        ^\n  22 |   });\n  23 |\n  24 |   test('non-custodian user → access restricted screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 24,
                            "line": 21
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n\n\n  19 |   test('no session → redirects to /hub/', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page).toHaveURL(/\\/hub\\//);\n     |                        ^\n  22 |   });\n  23 |\n  24 |   test('non-custodian user → access restricted screen', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:21:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:56:10.209Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6b1b7-session-→-redirects-to-hub--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6b1b7-session-→-redirects-to-hub--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 24,
                        "line": 21
                      }
                    },
                    {
                      "workerIndex": 93,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5290,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:21:24",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 24,
                          "line": 21
                        },
                        "snippet": "  19 |   test('no session → redirects to /hub/', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page).toHaveURL(/\\/hub\\//);\n     |                        ^\n  22 |   });\n  23 |\n  24 |   test('non-custodian user → access restricted screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 24,
                            "line": 21
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n\n\n  19 |   test('no session → redirects to /hub/', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page).toHaveURL(/\\/hub\\//);\n     |                        ^\n  22 |   });\n  23 |\n  24 |   test('non-custodian user → access restricted screen', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:21:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:56:16.563Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6b1b7-session-→-redirects-to-hub--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6b1b7-session-→-redirects-to-hub--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 24,
                        "line": 21
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-7c3909eeb38a71365cfc",
              "file": "custodian.spec.js",
              "line": 19,
              "column": 3
            },
            {
              "title": "non-custodian user → access restricted screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 94,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6265,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:27:58",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 58,
                          "line": 27
                        },
                        "snippet": "  25 |     await injectSession(page, 'mukesh', 'Mukesh');\n  26 |     await page.goto(BASE_URL);\n> 27 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n     |                                                          ^\n  28 |   });\n  29 | });\n  30 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 58,
                            "line": 27
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  25 |     await injectSession(page, 'mukesh', 'Mukesh');\n  26 |     await page.goto(BASE_URL);\n> 27 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n     |                                                          ^\n  28 |   });\n  29 | });\n  30 |\n    at /var/www/360lm/tests/custodian.spec.js:27:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:56:22.868Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-24fce--→-access-restricted-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-24fce--→-access-restricted-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 58,
                        "line": 27
                      }
                    },
                    {
                      "workerIndex": 95,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6265,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:27:58",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 58,
                          "line": 27
                        },
                        "snippet": "  25 |     await injectSession(page, 'mukesh', 'Mukesh');\n  26 |     await page.goto(BASE_URL);\n> 27 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n     |                                                          ^\n  28 |   });\n  29 | });\n  30 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 58,
                            "line": 27
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  25 |     await injectSession(page, 'mukesh', 'Mukesh');\n  26 |     await page.goto(BASE_URL);\n> 27 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n     |                                                          ^\n  28 |   });\n  29 | });\n  30 |\n    at /var/www/360lm/tests/custodian.spec.js:27:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:56:30.240Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-24fce--→-access-restricted-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-24fce--→-access-restricted-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 58,
                        "line": 27
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-7426706cb01e64c56b21",
              "file": "custodian.spec.js",
              "line": 24,
              "column": 3
            },
            {
              "title": "no session → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1099,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5230,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:21:24",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 24,
                          "line": 21
                        },
                        "snippet": "  19 |   test('no session → redirects to /hub/', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page).toHaveURL(/\\/hub\\//);\n     |                        ^\n  22 |   });\n  23 |\n  24 |   test('non-custodian user → access restricted screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 24,
                            "line": 21
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n\n\n  19 |   test('no session → redirects to /hub/', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page).toHaveURL(/\\/hub\\//);\n     |                        ^\n  22 |   });\n  23 |\n  24 |   test('non-custodian user → access restricted screen', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:21:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:55:49.326Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6b1b7-session-→-redirects-to-hub--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6b1b7-session-→-redirects-to-hub--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 24,
                        "line": 21
                      }
                    },
                    {
                      "workerIndex": 1100,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5292,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:21:24",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 24,
                          "line": 21
                        },
                        "snippet": "  19 |   test('no session → redirects to /hub/', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page).toHaveURL(/\\/hub\\//);\n     |                        ^\n  22 |   });\n  23 |\n  24 |   test('non-custodian user → access restricted screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 24,
                            "line": 21
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveURL\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/\\/hub\\//\u001b[39m\nReceived string:  \u001b[31m\"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveURL\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"https://srv1111289.hstgr.cloud/finance/custodian/\"\u001b[22m\n\n\n  19 |   test('no session → redirects to /hub/', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page).toHaveURL(/\\/hub\\//);\n     |                        ^\n  22 |   });\n  23 |\n  24 |   test('non-custodian user → access restricted screen', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:21:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:55:55.604Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6b1b7-session-→-redirects-to-hub--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6b1b7-session-→-redirects-to-hub--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 24,
                        "line": 21
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-c6f22316cb8cac2d41a0",
              "file": "custodian.spec.js",
              "line": 19,
              "column": 3
            },
            {
              "title": "non-custodian user → access restricted screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1101,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6284,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:27:58",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 58,
                          "line": 27
                        },
                        "snippet": "  25 |     await injectSession(page, 'mukesh', 'Mukesh');\n  26 |     await page.goto(BASE_URL);\n> 27 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n     |                                                          ^\n  28 |   });\n  29 | });\n  30 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 58,
                            "line": 27
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  25 |     await injectSession(page, 'mukesh', 'Mukesh');\n  26 |     await page.goto(BASE_URL);\n> 27 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n     |                                                          ^\n  28 |   });\n  29 | });\n  30 |\n    at /var/www/360lm/tests/custodian.spec.js:27:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:56:03.156Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-24fce--→-access-restricted-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-24fce--→-access-restricted-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 58,
                        "line": 27
                      }
                    },
                    {
                      "workerIndex": 1102,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6247,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:27:58",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 58,
                          "line": 27
                        },
                        "snippet": "  25 |     await injectSession(page, 'mukesh', 'Mukesh');\n  26 |     await page.goto(BASE_URL);\n> 27 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n     |                                                          ^\n  28 |   });\n  29 | });\n  30 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 58,
                            "line": 27
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  25 |     await injectSession(page, 'mukesh', 'Mukesh');\n  26 |     await page.goto(BASE_URL);\n> 27 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n     |                                                          ^\n  28 |   });\n  29 | });\n  30 |\n    at /var/www/360lm/tests/custodian.spec.js:27:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:56:10.637Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-24fce--→-access-restricted-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-24fce--→-access-restricted-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 58,
                        "line": 27
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-7a73166b5f4494b673b3",
              "file": "custodian.spec.js",
              "line": 24,
              "column": 3
            }
          ]
        },
        {
          "title": "Fund Custodian — Dashboard (Shambhu)",
          "file": "custodian.spec.js",
          "line": 31,
          "column": 6,
          "specs": [
            {
              "title": "dashboard loads with balance display",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 96,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8260,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:38:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 38
                        },
                        "snippet": "  36 |   test('dashboard loads with balance display', async ({ page }) => {\n  37 |     await page.goto(BASE_URL);\n> 38 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  39 |     await expect(page.locator('#my-balance')).toBeVisible();\n  40 |   });\n  41 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 38
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  36 |   test('dashboard loads with balance display', async ({ page }) => {\n  37 |     await page.goto(BASE_URL);\n> 38 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  39 |     await expect(page.locator('#my-balance')).toBeVisible();\n  40 |   });\n  41 |\n    at /var/www/360lm/tests/custodian.spec.js:38:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:56:37.549Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-5a159--loads-with-balance-display-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-5a159--loads-with-balance-display-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 38
                      }
                    },
                    {
                      "workerIndex": 97,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8260,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:38:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 38
                        },
                        "snippet": "  36 |   test('dashboard loads with balance display', async ({ page }) => {\n  37 |     await page.goto(BASE_URL);\n> 38 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  39 |     await expect(page.locator('#my-balance')).toBeVisible();\n  40 |   });\n  41 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 38
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  36 |   test('dashboard loads with balance display', async ({ page }) => {\n  37 |     await page.goto(BASE_URL);\n> 38 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  39 |     await expect(page.locator('#my-balance')).toBeVisible();\n  40 |   });\n  41 |\n    at /var/www/360lm/tests/custodian.spec.js:38:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:56:46.902Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-5a159--loads-with-balance-display-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-5a159--loads-with-balance-display-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 38
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-fd3e3ceb54b6585a881b",
              "file": "custodian.spec.js",
              "line": 36,
              "column": 3
            },
            {
              "title": "bottom nav shows 3 tabs (advance merged into transfer in v6)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 98,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5328,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:44:45",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 45,
                          "line": 44
                        },
                        "snippet": "  42 |   test('bottom nav shows 3 tabs (advance merged into transfer in v6)', async ({ page }) => {\n  43 |     await page.goto(BASE_URL);\n> 44 |     await expect(page.locator('.bnav-btn')).toHaveCount(3);\n     |                                             ^\n  45 |   });\n  46 |\n  47 |   test('manage button hidden for non-admin', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 45,
                            "line": 44
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  42 |   test('bottom nav shows 3 tabs (advance merged into transfer in v6)', async ({ page }) => {\n  43 |     await page.goto(BASE_URL);\n> 44 |     await expect(page.locator('.bnav-btn')).toHaveCount(3);\n     |                                             ^\n  45 |   });\n  46 |\n  47 |   test('manage button hidden for non-admin', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:44:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:56:56.227Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-0bec2-merged-into-transfer-in-v6--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-0bec2-merged-into-transfer-in-v6--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 45,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 99,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5393,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:44:45",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 45,
                          "line": 44
                        },
                        "snippet": "  42 |   test('bottom nav shows 3 tabs (advance merged into transfer in v6)', async ({ page }) => {\n  43 |     await page.goto(BASE_URL);\n> 44 |     await expect(page.locator('.bnav-btn')).toHaveCount(3);\n     |                                             ^\n  45 |   });\n  46 |\n  47 |   test('manage button hidden for non-admin', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 45,
                            "line": 44
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  42 |   test('bottom nav shows 3 tabs (advance merged into transfer in v6)', async ({ page }) => {\n  43 |     await page.goto(BASE_URL);\n> 44 |     await expect(page.locator('.bnav-btn')).toHaveCount(3);\n     |                                             ^\n  45 |   });\n  46 |\n  47 |   test('manage button hidden for non-admin', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:44:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:57:03.255Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-0bec2-merged-into-transfer-in-v6--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-0bec2-merged-into-transfer-in-v6--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 45,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-546732c9e3d94d54381c",
              "file": "custodian.spec.js",
              "line": 42,
              "column": 3
            },
            {
              "title": "manage button hidden for non-admin",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 100,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 355,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:57:09.946Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e1e49def2648bf8aa609-32179834a5e81e3bd924",
              "file": "custodian.spec.js",
              "line": 47,
              "column": 3
            },
            {
              "title": "dashboard loads with balance display",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1103,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8241,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:38:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 38
                        },
                        "snippet": "  36 |   test('dashboard loads with balance display', async ({ page }) => {\n  37 |     await page.goto(BASE_URL);\n> 38 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  39 |     await expect(page.locator('#my-balance')).toBeVisible();\n  40 |   });\n  41 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 38
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  36 |   test('dashboard loads with balance display', async ({ page }) => {\n  37 |     await page.goto(BASE_URL);\n> 38 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  39 |     await expect(page.locator('#my-balance')).toBeVisible();\n  40 |   });\n  41 |\n    at /var/www/360lm/tests/custodian.spec.js:38:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:56:17.815Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-5a159--loads-with-balance-display-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-5a159--loads-with-balance-display-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 38
                      }
                    },
                    {
                      "workerIndex": 1104,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8217,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:38:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 38
                        },
                        "snippet": "  36 |   test('dashboard loads with balance display', async ({ page }) => {\n  37 |     await page.goto(BASE_URL);\n> 38 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  39 |     await expect(page.locator('#my-balance')).toBeVisible();\n  40 |   });\n  41 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 38
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  36 |   test('dashboard loads with balance display', async ({ page }) => {\n  37 |     await page.goto(BASE_URL);\n> 38 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  39 |     await expect(page.locator('#my-balance')).toBeVisible();\n  40 |   });\n  41 |\n    at /var/www/360lm/tests/custodian.spec.js:38:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:56:27.068Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-5a159--loads-with-balance-display-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-5a159--loads-with-balance-display-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 38
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-52c08784c877939e3495",
              "file": "custodian.spec.js",
              "line": 36,
              "column": 3
            },
            {
              "title": "bottom nav shows 3 tabs (advance merged into transfer in v6)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1105,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5252,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:44:45",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 45,
                          "line": 44
                        },
                        "snippet": "  42 |   test('bottom nav shows 3 tabs (advance merged into transfer in v6)', async ({ page }) => {\n  43 |     await page.goto(BASE_URL);\n> 44 |     await expect(page.locator('.bnav-btn')).toHaveCount(3);\n     |                                             ^\n  45 |   });\n  46 |\n  47 |   test('manage button hidden for non-admin', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 45,
                            "line": 44
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  42 |   test('bottom nav shows 3 tabs (advance merged into transfer in v6)', async ({ page }) => {\n  43 |     await page.goto(BASE_URL);\n> 44 |     await expect(page.locator('.bnav-btn')).toHaveCount(3);\n     |                                             ^\n  45 |   });\n  46 |\n  47 |   test('manage button hidden for non-admin', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:44:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:56:36.236Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-0bec2-merged-into-transfer-in-v6--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-0bec2-merged-into-transfer-in-v6--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 45,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 1106,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5252,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:44:45",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 45,
                          "line": 44
                        },
                        "snippet": "  42 |   test('bottom nav shows 3 tabs (advance merged into transfer in v6)', async ({ page }) => {\n  43 |     await page.goto(BASE_URL);\n> 44 |     await expect(page.locator('.bnav-btn')).toHaveCount(3);\n     |                                             ^\n  45 |   });\n  46 |\n  47 |   test('manage button hidden for non-admin', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 45,
                            "line": 44
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('.bnav-btn')\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.bnav-btn')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  42 |   test('bottom nav shows 3 tabs (advance merged into transfer in v6)', async ({ page }) => {\n  43 |     await page.goto(BASE_URL);\n> 44 |     await expect(page.locator('.bnav-btn')).toHaveCount(3);\n     |                                             ^\n  45 |   });\n  46 |\n  47 |   test('manage button hidden for non-admin', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:44:45"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:56:42.471Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-0bec2-merged-into-transfer-in-v6--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-0bec2-merged-into-transfer-in-v6--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 45,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-44df80ef47e6d51dd63f",
              "file": "custodian.spec.js",
              "line": 42,
              "column": 3
            },
            {
              "title": "manage button hidden for non-admin",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1107,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 328,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:56:48.803Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e1e49def2648bf8aa609-e4db157dbd78db522898",
              "file": "custodian.spec.js",
              "line": 47,
              "column": 3
            }
          ]
        },
        {
          "title": "Fund Custodian — Dashboard (Harish / Admin)",
          "file": "custodian.spec.js",
          "line": 53,
          "column": 6,
          "specs": [
            {
              "title": "manage button visible for admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 100,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8197,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:60:51",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 51,
                          "line": 60
                        },
                        "snippet": "  58 |   test('manage button visible for admin', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n     |                                                   ^\n  61 |   });\n  62 |\n  63 |   test('all custodian balances section loads for admin (overseer mode)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 51,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  58 |   test('manage button visible for admin', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n     |                                                   ^\n  61 |   });\n  62 |\n  63 |   test('all custodian balances section loads for admin (overseer mode)', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:60:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:57:10.480Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4d164-ge-button-visible-for-admin-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4d164-ge-button-visible-for-admin-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 51,
                        "line": 60
                      }
                    },
                    {
                      "workerIndex": 101,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8270,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:60:51",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 51,
                          "line": 60
                        },
                        "snippet": "  58 |   test('manage button visible for admin', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n     |                                                   ^\n  61 |   });\n  62 |\n  63 |   test('all custodian balances section loads for admin (overseer mode)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 51,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  58 |   test('manage button visible for admin', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n     |                                                   ^\n  61 |   });\n  62 |\n  63 |   test('all custodian balances section loads for admin (overseer mode)', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:60:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:57:19.769Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4d164-ge-button-visible-for-admin-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4d164-ge-button-visible-for-admin-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 51,
                        "line": 60
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-c6ff855bf2f776bcb155",
              "file": "custodian.spec.js",
              "line": 58,
              "column": 3
            },
            {
              "title": "all custodian balances section loads for admin (overseer mode)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 102,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30067,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 63,
                            "line": 66
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.mode-card').filter({ hasText: 'Overview' })\u001b[22m\n\n\n  64 |     await page.goto(BASE_URL);\n  65 |     // Admin lands on mode selector — pick Overseer to see all balances\n> 66 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n     |                                                               ^\n  67 |     await expect(page.locator('.sec-label:has-text(\"All Custodian Balances\")')).toBeVisible({ timeout: 8000 });\n  68 |   });\n  69 | });\n    at /var/www/360lm/tests/custodian.spec.js:66:63"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:57:29.148Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-cd717-ds-for-admin-overseer-mode--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-cd717-ds-for-admin-overseer-mode--android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 103,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30096,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 63,
                            "line": 66
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.mode-card').filter({ hasText: 'Overview' })\u001b[22m\n\n\n  64 |     await page.goto(BASE_URL);\n  65 |     // Admin lands on mode selector — pick Overseer to see all balances\n> 66 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n     |                                                               ^\n  67 |     await expect(page.locator('.sec-label:has-text(\"All Custodian Balances\")')).toBeVisible({ timeout: 8000 });\n  68 |   });\n  69 | });\n    at /var/www/360lm/tests/custodian.spec.js:66:63"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:58:00.355Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-cd717-ds-for-admin-overseer-mode--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-cd717-ds-for-admin-overseer-mode--android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-09b6ad085f5f3f0028ee",
              "file": "custodian.spec.js",
              "line": 63,
              "column": 3
            },
            {
              "title": "manage button visible for admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1107,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8183,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:60:51",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 51,
                          "line": 60
                        },
                        "snippet": "  58 |   test('manage button visible for admin', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n     |                                                   ^\n  61 |   });\n  62 |\n  63 |   test('all custodian balances section loads for admin (overseer mode)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 51,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  58 |   test('manage button visible for admin', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n     |                                                   ^\n  61 |   });\n  62 |\n  63 |   test('all custodian balances section loads for admin (overseer mode)', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:60:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:56:49.306Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4d164-ge-button-visible-for-admin-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4d164-ge-button-visible-for-admin-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 51,
                        "line": 60
                      }
                    },
                    {
                      "workerIndex": 1108,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8255,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:60:51",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 51,
                          "line": 60
                        },
                        "snippet": "  58 |   test('manage button visible for admin', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n     |                                                   ^\n  61 |   });\n  62 |\n  63 |   test('all custodian balances section loads for admin (overseer mode)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 51,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  58 |   test('manage button visible for admin', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n     |                                                   ^\n  61 |   });\n  62 |\n  63 |   test('all custodian balances section loads for admin (overseer mode)', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:60:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:56:58.353Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4d164-ge-button-visible-for-admin-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4d164-ge-button-visible-for-admin-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 51,
                        "line": 60
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-5aa0a1294f0f5eb5c743",
              "file": "custodian.spec.js",
              "line": 58,
              "column": 3
            },
            {
              "title": "all custodian balances section loads for admin (overseer mode)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1109,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30086,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 63,
                            "line": 66
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.mode-card').filter({ hasText: 'Overview' })\u001b[22m\n\n\n  64 |     await page.goto(BASE_URL);\n  65 |     // Admin lands on mode selector — pick Overseer to see all balances\n> 66 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n     |                                                               ^\n  67 |     await expect(page.locator('.sec-label:has-text(\"All Custodian Balances\")')).toBeVisible({ timeout: 8000 });\n  68 |   });\n  69 | });\n    at /var/www/360lm/tests/custodian.spec.js:66:63"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:57:07.596Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-cd717-ds-for-admin-overseer-mode--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-cd717-ds-for-admin-overseer-mode--desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1110,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30105,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 63,
                            "line": 66
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.mode-card').filter({ hasText: 'Overview' })\u001b[22m\n\n\n  64 |     await page.goto(BASE_URL);\n  65 |     // Admin lands on mode selector — pick Overseer to see all balances\n> 66 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n     |                                                               ^\n  67 |     await expect(page.locator('.sec-label:has-text(\"All Custodian Balances\")')).toBeVisible({ timeout: 8000 });\n  68 |   });\n  69 | });\n    at /var/www/360lm/tests/custodian.spec.js:66:63"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:57:38.698Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-cd717-ds-for-admin-overseer-mode--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-cd717-ds-for-admin-overseer-mode--desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-87a264c48d20d8a5aa6e",
              "file": "custodian.spec.js",
              "line": 63,
              "column": 3
            }
          ]
        },
        {
          "title": "Fund Custodian — Transfer Form",
          "file": "custodian.spec.js",
          "line": 71,
          "column": 6,
          "specs": [
            {
              "title": "transfer screen shows role picker and form",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 104,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30345,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 78
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  76 |   test('transfer screen shows role picker and form', async ({ page }) => {\n  77 |     await page.goto(BASE_URL);\n> 78 |     await page.click('#nav-transfer');\n     |                ^\n  79 |     await expect(page.locator('#role-giver')).toBeVisible({ timeout: 6000 });\n  80 |     await expect(page.locator('#role-receiver')).toBeVisible();\n  81 |     await expect(page.locator('#tf-amount')).toBeVisible();\n    at /var/www/360lm/tests/custodian.spec.js:78:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:58:31.508Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-440c0--shows-role-picker-and-form-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-440c0--shows-role-picker-and-form-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 105,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30099,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 78
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  76 |   test('transfer screen shows role picker and form', async ({ page }) => {\n  77 |     await page.goto(BASE_URL);\n> 78 |     await page.click('#nav-transfer');\n     |                ^\n  79 |     await expect(page.locator('#role-giver')).toBeVisible({ timeout: 6000 });\n  80 |     await expect(page.locator('#role-receiver')).toBeVisible();\n  81 |     await expect(page.locator('#tf-amount')).toBeVisible();\n    at /var/www/360lm/tests/custodian.spec.js:78:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T18:59:03.516Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-440c0--shows-role-picker-and-form-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-440c0--shows-role-picker-and-form-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-b9262bb9db98f1b8aba7",
              "file": "custodian.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "role-giver selected by default",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 106,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30403,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 86
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  84 |   test('role-giver selected by default', async ({ page }) => {\n  85 |     await page.goto(BASE_URL);\n> 86 |     await page.click('#nav-transfer');\n     |                ^\n  87 |     await expect(page.locator('#role-giver')).toHaveClass(/sel/);\n  88 |   });\n  89 |\n    at /var/www/360lm/tests/custodian.spec.js:86:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T18:59:34.864Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09eb7-e-giver-selected-by-default-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09eb7-e-giver-selected-by-default-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 107,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30103,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 86
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  84 |   test('role-giver selected by default', async ({ page }) => {\n  85 |     await page.goto(BASE_URL);\n> 86 |     await page.click('#nav-transfer');\n     |                ^\n  87 |     await expect(page.locator('#role-giver')).toHaveClass(/sel/);\n  88 |   });\n  89 |\n    at /var/www/360lm/tests/custodian.spec.js:86:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:00:07.718Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09eb7-e-giver-selected-by-default-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09eb7-e-giver-selected-by-default-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-0d65a29db5dc8c28d5f4",
              "file": "custodian.spec.js",
              "line": 84,
              "column": 3
            },
            {
              "title": "clicking role-receiver switches selection",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 108,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30105,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 92
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  90 |   test('clicking role-receiver switches selection', async ({ page }) => {\n  91 |     await page.goto(BASE_URL);\n> 92 |     await page.click('#nav-transfer');\n     |                ^\n  93 |     await page.click('#role-receiver');\n  94 |     await expect(page.locator('#role-receiver')).toHaveClass(/sel/);\n  95 |     await expect(page.locator('#role-giver')).not.toHaveClass(/sel/);\n    at /var/www/360lm/tests/custodian.spec.js:92:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:00:39.378Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4735c-receiver-switches-selection-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4735c-receiver-switches-selection-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 109,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30103,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 92
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  90 |   test('clicking role-receiver switches selection', async ({ page }) => {\n  91 |     await page.goto(BASE_URL);\n> 92 |     await page.click('#nav-transfer');\n     |                ^\n  93 |     await page.click('#role-receiver');\n  94 |     await expect(page.locator('#role-receiver')).toHaveClass(/sel/);\n  95 |     await expect(page.locator('#role-giver')).not.toHaveClass(/sel/);\n    at /var/www/360lm/tests/custodian.spec.js:92:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:01:10.620Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4735c-receiver-switches-selection-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4735c-receiver-switches-selection-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-06537cacc285d5fac8ac",
              "file": "custodian.spec.js",
              "line": 90,
              "column": 3
            },
            {
              "title": "submit with no amount shows validation toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 110,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 100
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n   98 |   test('submit with no amount shows validation toast', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.click('#nav-transfer');\n      |                ^\n  101 |     await page.click('#tf-submit');\n  102 |     await expect(page.locator('#toast.t-err')).toBeVisible({ timeout: 4000 });\n  103 |   });\n    at /var/www/360lm/tests/custodian.spec.js:100:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:01:41.795Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-88864-ount-shows-validation-toast-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-88864-ount-shows-validation-toast-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 111,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30081,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 100
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n   98 |   test('submit with no amount shows validation toast', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.click('#nav-transfer');\n      |                ^\n  101 |     await page.click('#tf-submit');\n  102 |     await expect(page.locator('#toast.t-err')).toBeVisible({ timeout: 4000 });\n  103 |   });\n    at /var/www/360lm/tests/custodian.spec.js:100:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:02:12.915Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-88864-ount-shows-validation-toast-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-88864-ount-shows-validation-toast-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-2f9fd31ee3571aecaf51",
              "file": "custodian.spec.js",
              "line": 98,
              "column": 3
            },
            {
              "title": "cash + giving shows pending warning on screen open",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 112,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 107
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  105 |   test('cash + giving shows pending warning on screen open', async ({ page }) => {\n  106 |     await page.goto(BASE_URL);\n> 107 |     await page.click('#nav-transfer');\n      |                ^\n  108 |     await expect(page.locator('#tf-pending-info')).toBeVisible({ timeout: 4000 });\n  109 |   });\n  110 |\n    at /var/www/360lm/tests/custodian.spec.js:107:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:02:44.095Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-9301b-ding-warning-on-screen-open-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-9301b-ding-warning-on-screen-open-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 113,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 107
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  105 |   test('cash + giving shows pending warning on screen open', async ({ page }) => {\n  106 |     await page.goto(BASE_URL);\n> 107 |     await page.click('#nav-transfer');\n      |                ^\n  108 |     await expect(page.locator('#tf-pending-info')).toBeVisible({ timeout: 4000 });\n  109 |   });\n  110 |\n    at /var/www/360lm/tests/custodian.spec.js:107:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:03:15.179Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-9301b-ding-warning-on-screen-open-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-9301b-ding-warning-on-screen-open-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-1fff4b23b460be04a123",
              "file": "custodian.spec.js",
              "line": 105,
              "column": 3
            },
            {
              "title": "switching to receiver hides pending warning",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 114,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30068,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 113
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  111 |   test('switching to receiver hides pending warning', async ({ page }) => {\n  112 |     await page.goto(BASE_URL);\n> 113 |     await page.click('#nav-transfer');\n      |                ^\n  114 |     await page.click('#role-receiver');\n  115 |     await expect(page.locator('#tf-pending-info')).toBeHidden();\n  116 |   });\n    at /var/www/360lm/tests/custodian.spec.js:113:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:03:46.237Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-99d1c-eiver-hides-pending-warning-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-99d1c-eiver-hides-pending-warning-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 115,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30083,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 113
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  111 |   test('switching to receiver hides pending warning', async ({ page }) => {\n  112 |     await page.goto(BASE_URL);\n> 113 |     await page.click('#nav-transfer');\n      |                ^\n  114 |     await page.click('#role-receiver');\n  115 |     await expect(page.locator('#tf-pending-info')).toBeHidden();\n  116 |   });\n    at /var/www/360lm/tests/custodian.spec.js:113:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:04:17.290Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-99d1c-eiver-hides-pending-warning-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-99d1c-eiver-hides-pending-warning-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-3eb568aa5b5bf5ab757a",
              "file": "custodian.spec.js",
              "line": 111,
              "column": 3
            },
            {
              "title": "transfer screen shows role picker and form",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1111,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30088,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 78
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  76 |   test('transfer screen shows role picker and form', async ({ page }) => {\n  77 |     await page.goto(BASE_URL);\n> 78 |     await page.click('#nav-transfer');\n     |                ^\n  79 |     await expect(page.locator('#role-giver')).toBeVisible({ timeout: 6000 });\n  80 |     await expect(page.locator('#role-receiver')).toBeVisible();\n  81 |     await expect(page.locator('#tf-amount')).toBeVisible();\n    at /var/www/360lm/tests/custodian.spec.js:78:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:58:09.885Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-440c0--shows-role-picker-and-form-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-440c0--shows-role-picker-and-form-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1112,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30080,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 78
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  76 |   test('transfer screen shows role picker and form', async ({ page }) => {\n  77 |     await page.goto(BASE_URL);\n> 78 |     await page.click('#nav-transfer');\n     |                ^\n  79 |     await expect(page.locator('#role-giver')).toBeVisible({ timeout: 6000 });\n  80 |     await expect(page.locator('#role-receiver')).toBeVisible();\n  81 |     await expect(page.locator('#tf-amount')).toBeVisible();\n    at /var/www/360lm/tests/custodian.spec.js:78:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:58:41.029Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-440c0--shows-role-picker-and-form-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-440c0--shows-role-picker-and-form-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-ca479c441790f36ac552",
              "file": "custodian.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "role-giver selected by default",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1113,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30075,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 86
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  84 |   test('role-giver selected by default', async ({ page }) => {\n  85 |     await page.goto(BASE_URL);\n> 86 |     await page.click('#nav-transfer');\n     |                ^\n  87 |     await expect(page.locator('#role-giver')).toHaveClass(/sel/);\n  88 |   });\n  89 |\n    at /var/www/360lm/tests/custodian.spec.js:86:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:59:12.067Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09eb7-e-giver-selected-by-default-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09eb7-e-giver-selected-by-default-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1114,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30096,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 86
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  84 |   test('role-giver selected by default', async ({ page }) => {\n  85 |     await page.goto(BASE_URL);\n> 86 |     await page.click('#nav-transfer');\n     |                ^\n  87 |     await expect(page.locator('#role-giver')).toHaveClass(/sel/);\n  88 |   });\n  89 |\n    at /var/www/360lm/tests/custodian.spec.js:86:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:59:43.080Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09eb7-e-giver-selected-by-default-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09eb7-e-giver-selected-by-default-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-56b37f401306d839492e",
              "file": "custodian.spec.js",
              "line": 84,
              "column": 3
            },
            {
              "title": "clicking role-receiver switches selection",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1115,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30089,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 92
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  90 |   test('clicking role-receiver switches selection', async ({ page }) => {\n  91 |     await page.goto(BASE_URL);\n> 92 |     await page.click('#nav-transfer');\n     |                ^\n  93 |     await page.click('#role-receiver');\n  94 |     await expect(page.locator('#role-receiver')).toHaveClass(/sel/);\n  95 |     await expect(page.locator('#role-giver')).not.toHaveClass(/sel/);\n    at /var/www/360lm/tests/custodian.spec.js:92:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:00:14.397Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4735c-receiver-switches-selection-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4735c-receiver-switches-selection-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1116,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30090,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 92
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  90 |   test('clicking role-receiver switches selection', async ({ page }) => {\n  91 |     await page.goto(BASE_URL);\n> 92 |     await page.click('#nav-transfer');\n     |                ^\n  93 |     await page.click('#role-receiver');\n  94 |     await expect(page.locator('#role-receiver')).toHaveClass(/sel/);\n  95 |     await expect(page.locator('#role-giver')).not.toHaveClass(/sel/);\n    at /var/www/360lm/tests/custodian.spec.js:92:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:00:45.641Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4735c-receiver-switches-selection-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-4735c-receiver-switches-selection-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-fce0ec81d7beb36c754c",
              "file": "custodian.spec.js",
              "line": 90,
              "column": 3
            },
            {
              "title": "submit with no amount shows validation toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1117,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30096,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 100
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n   98 |   test('submit with no amount shows validation toast', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.click('#nav-transfer');\n      |                ^\n  101 |     await page.click('#tf-submit');\n  102 |     await expect(page.locator('#toast.t-err')).toBeVisible({ timeout: 4000 });\n  103 |   });\n    at /var/www/360lm/tests/custodian.spec.js:100:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:01:16.861Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-88864-ount-shows-validation-toast-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-88864-ount-shows-validation-toast-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1118,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30080,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 100
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n   98 |   test('submit with no amount shows validation toast', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.click('#nav-transfer');\n      |                ^\n  101 |     await page.click('#tf-submit');\n  102 |     await expect(page.locator('#toast.t-err')).toBeVisible({ timeout: 4000 });\n  103 |   });\n    at /var/www/360lm/tests/custodian.spec.js:100:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:01:47.997Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-88864-ount-shows-validation-toast-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-88864-ount-shows-validation-toast-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-98b55bfb9c324d07d12b",
              "file": "custodian.spec.js",
              "line": 98,
              "column": 3
            },
            {
              "title": "cash + giving shows pending warning on screen open",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1119,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 107
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  105 |   test('cash + giving shows pending warning on screen open', async ({ page }) => {\n  106 |     await page.goto(BASE_URL);\n> 107 |     await page.click('#nav-transfer');\n      |                ^\n  108 |     await expect(page.locator('#tf-pending-info')).toBeVisible({ timeout: 4000 });\n  109 |   });\n  110 |\n    at /var/www/360lm/tests/custodian.spec.js:107:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:02:19.115Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-9301b-ding-warning-on-screen-open-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-9301b-ding-warning-on-screen-open-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1120,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 107
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  105 |   test('cash + giving shows pending warning on screen open', async ({ page }) => {\n  106 |     await page.goto(BASE_URL);\n> 107 |     await page.click('#nav-transfer');\n      |                ^\n  108 |     await expect(page.locator('#tf-pending-info')).toBeVisible({ timeout: 4000 });\n  109 |   });\n  110 |\n    at /var/www/360lm/tests/custodian.spec.js:107:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:02:50.233Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-9301b-ding-warning-on-screen-open-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-9301b-ding-warning-on-screen-open-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-0ba3cd976d05cb73cf9c",
              "file": "custodian.spec.js",
              "line": 105,
              "column": 3
            },
            {
              "title": "switching to receiver hides pending warning",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1121,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30060,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 113
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  111 |   test('switching to receiver hides pending warning', async ({ page }) => {\n  112 |     await page.goto(BASE_URL);\n> 113 |     await page.click('#nav-transfer');\n      |                ^\n  114 |     await page.click('#role-receiver');\n  115 |     await expect(page.locator('#tf-pending-info')).toBeHidden();\n  116 |   });\n    at /var/www/360lm/tests/custodian.spec.js:113:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:03:21.240Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-99d1c-eiver-hides-pending-warning-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-99d1c-eiver-hides-pending-warning-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1122,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 113
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  111 |   test('switching to receiver hides pending warning', async ({ page }) => {\n  112 |     await page.goto(BASE_URL);\n> 113 |     await page.click('#nav-transfer');\n      |                ^\n  114 |     await page.click('#role-receiver');\n  115 |     await expect(page.locator('#tf-pending-info')).toBeHidden();\n  116 |   });\n    at /var/www/360lm/tests/custodian.spec.js:113:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:03:52.338Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-99d1c-eiver-hides-pending-warning-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-99d1c-eiver-hides-pending-warning-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-3f260cae332e61042918",
              "file": "custodian.spec.js",
              "line": 111,
              "column": 3
            }
          ]
        },
        {
          "title": "Fund Custodian — Employee transfer (advance via unified form)",
          "file": "custodian.spec.js",
          "line": 121,
          "column": 6,
          "specs": [
            {
              "title": "Employee category reveals people search + advance type",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 116,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 128
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  126 |   test('Employee category reveals people search + advance type', async ({ page }) => {\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.click('#nav-transfer');\n      |                ^\n  129 |     await page.locator('#tf-cat-chips [data-cat=\"employee\"]').click();\n  130 |     await expect(page.locator('#tf-ps-q')).toBeVisible({ timeout: 6000 });\n  131 |     await expect(page.locator('#tf-advtype-section')).toBeVisible();\n    at /var/www/360lm/tests/custodian.spec.js:128:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:04:48.412Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-30c8c--people-search-advance-type-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-30c8c--people-search-advance-type-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 117,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30094,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 128
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  126 |   test('Employee category reveals people search + advance type', async ({ page }) => {\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.click('#nav-transfer');\n      |                ^\n  129 |     await page.locator('#tf-cat-chips [data-cat=\"employee\"]').click();\n  130 |     await expect(page.locator('#tf-ps-q')).toBeVisible({ timeout: 6000 });\n  131 |     await expect(page.locator('#tf-advtype-section')).toBeVisible();\n    at /var/www/360lm/tests/custodian.spec.js:128:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:05:19.589Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-30c8c--people-search-advance-type-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-30c8c--people-search-advance-type-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-2e06443fe65f36b76376",
              "file": "custodian.spec.js",
              "line": 126,
              "column": 3
            },
            {
              "title": "submit employee transfer with no payee shows validation toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 118,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30080,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 136
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  134 |   test('submit employee transfer with no payee shows validation toast', async ({ page }) => {\n  135 |     await page.goto(BASE_URL);\n> 136 |     await page.click('#nav-transfer');\n      |                ^\n  137 |     await page.locator('#tf-cat-chips [data-cat=\"employee\"]').click();\n  138 |     await page.locator('#tf-amount').fill('500');\n  139 |     await page.locator('#tf-submit').click();\n    at /var/www/360lm/tests/custodian.spec.js:136:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:05:50.706Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3a78b-ayee-shows-validation-toast-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3a78b-ayee-shows-validation-toast-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 119,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30103,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 136
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  134 |   test('submit employee transfer with no payee shows validation toast', async ({ page }) => {\n  135 |     await page.goto(BASE_URL);\n> 136 |     await page.click('#nav-transfer');\n      |                ^\n  137 |     await page.locator('#tf-cat-chips [data-cat=\"employee\"]').click();\n  138 |     await page.locator('#tf-amount').fill('500');\n  139 |     await page.locator('#tf-submit').click();\n    at /var/www/360lm/tests/custodian.spec.js:136:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:06:21.802Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3a78b-ayee-shows-validation-toast-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3a78b-ayee-shows-validation-toast-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-2495fbec30ef40433f53",
              "file": "custodian.spec.js",
              "line": 134,
              "column": 3
            },
            {
              "title": "Employee category reveals people search + advance type",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1123,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30064,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 128
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  126 |   test('Employee category reveals people search + advance type', async ({ page }) => {\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.click('#nav-transfer');\n      |                ^\n  129 |     await page.locator('#tf-cat-chips [data-cat=\"employee\"]').click();\n  130 |     await expect(page.locator('#tf-ps-q')).toBeVisible({ timeout: 6000 });\n  131 |     await expect(page.locator('#tf-advtype-section')).toBeVisible();\n    at /var/www/360lm/tests/custodian.spec.js:128:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:04:23.418Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-30c8c--people-search-advance-type-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-30c8c--people-search-advance-type-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1124,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30080,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 128
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  126 |   test('Employee category reveals people search + advance type', async ({ page }) => {\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.click('#nav-transfer');\n      |                ^\n  129 |     await page.locator('#tf-cat-chips [data-cat=\"employee\"]').click();\n  130 |     await expect(page.locator('#tf-ps-q')).toBeVisible({ timeout: 6000 });\n  131 |     await expect(page.locator('#tf-advtype-section')).toBeVisible();\n    at /var/www/360lm/tests/custodian.spec.js:128:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:04:54.443Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-30c8c--people-search-advance-type-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-30c8c--people-search-advance-type-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-f963b0e07fe4f039fd40",
              "file": "custodian.spec.js",
              "line": 126,
              "column": 3
            },
            {
              "title": "submit employee transfer with no payee shows validation toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1125,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30071,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 136
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  134 |   test('submit employee transfer with no payee shows validation toast', async ({ page }) => {\n  135 |     await page.goto(BASE_URL);\n> 136 |     await page.click('#nav-transfer');\n      |                ^\n  137 |     await page.locator('#tf-cat-chips [data-cat=\"employee\"]').click();\n  138 |     await page.locator('#tf-amount').fill('500');\n  139 |     await page.locator('#tf-submit').click();\n    at /var/www/360lm/tests/custodian.spec.js:136:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:05:25.518Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3a78b-ayee-shows-validation-toast-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3a78b-ayee-shows-validation-toast-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1126,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30081,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 16,
                            "line": 136
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  134 |   test('submit employee transfer with no payee shows validation toast', async ({ page }) => {\n  135 |     await page.goto(BASE_URL);\n> 136 |     await page.click('#nav-transfer');\n      |                ^\n  137 |     await page.locator('#tf-cat-chips [data-cat=\"employee\"]').click();\n  138 |     await page.locator('#tf-amount').fill('500');\n  139 |     await page.locator('#tf-submit').click();\n    at /var/www/360lm/tests/custodian.spec.js:136:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:05:56.499Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3a78b-ayee-shows-validation-toast-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3a78b-ayee-shows-validation-toast-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-47fb3ada919d5a14f192",
              "file": "custodian.spec.js",
              "line": 134,
              "column": 3
            }
          ]
        },
        {
          "title": "Fund Custodian — Offline",
          "file": "custodian.spec.js",
          "line": 144,
          "column": 6,
          "specs": [
            {
              "title": "UI remains functional when network drops mid-session",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 120,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8250,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:148:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 148
                        },
                        "snippet": "  146 |     await injectSession(page, 'shambhu', 'Shambhu');\n  147 |     await page.goto(BASE_URL);\n> 148 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n      |                                           ^\n  149 |     // Simulate network drop after page loads — UI should not crash\n  150 |     await context.setOffline(true);\n  151 |     await page.click('#nav-transfer');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 148
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  146 |     await injectSession(page, 'shambhu', 'Shambhu');\n  147 |     await page.goto(BASE_URL);\n> 148 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n      |                                           ^\n  149 |     // Simulate network drop after page loads — UI should not crash\n  150 |     await context.setOffline(true);\n  151 |     await page.click('#nav-transfer');\n    at /var/www/360lm/tests/custodian.spec.js:148:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:06:52.880Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-361ba-n-network-drops-mid-session-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-361ba-n-network-drops-mid-session-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 148
                      }
                    },
                    {
                      "workerIndex": 121,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8311,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:148:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 148
                        },
                        "snippet": "  146 |     await injectSession(page, 'shambhu', 'Shambhu');\n  147 |     await page.goto(BASE_URL);\n> 148 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n      |                                           ^\n  149 |     // Simulate network drop after page loads — UI should not crash\n  150 |     await context.setOffline(true);\n  151 |     await page.click('#nav-transfer');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 148
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  146 |     await injectSession(page, 'shambhu', 'Shambhu');\n  147 |     await page.goto(BASE_URL);\n> 148 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n      |                                           ^\n  149 |     // Simulate network drop after page loads — UI should not crash\n  150 |     await context.setOffline(true);\n  151 |     await page.click('#nav-transfer');\n    at /var/www/360lm/tests/custodian.spec.js:148:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:07:02.598Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-361ba-n-network-drops-mid-session-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-361ba-n-network-drops-mid-session-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 148
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-d3341a8c5b518c72ac5b",
              "file": "custodian.spec.js",
              "line": 145,
              "column": 3
            },
            {
              "title": "UI remains functional when network drops mid-session",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1127,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8260,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:148:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 148
                        },
                        "snippet": "  146 |     await injectSession(page, 'shambhu', 'Shambhu');\n  147 |     await page.goto(BASE_URL);\n> 148 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n      |                                           ^\n  149 |     // Simulate network drop after page loads — UI should not crash\n  150 |     await context.setOffline(true);\n  151 |     await page.click('#nav-transfer');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 148
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  146 |     await injectSession(page, 'shambhu', 'Shambhu');\n  147 |     await page.goto(BASE_URL);\n> 148 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n      |                                           ^\n  149 |     // Simulate network drop after page loads — UI should not crash\n  150 |     await context.setOffline(true);\n  151 |     await page.click('#nav-transfer');\n    at /var/www/360lm/tests/custodian.spec.js:148:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:06:27.558Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-361ba-n-network-drops-mid-session-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-361ba-n-network-drops-mid-session-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 148
                      }
                    },
                    {
                      "workerIndex": 1128,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8229,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:148:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 148
                        },
                        "snippet": "  146 |     await injectSession(page, 'shambhu', 'Shambhu');\n  147 |     await page.goto(BASE_URL);\n> 148 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n      |                                           ^\n  149 |     // Simulate network drop after page loads — UI should not crash\n  150 |     await context.setOffline(true);\n  151 |     await page.click('#nav-transfer');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 148
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  146 |     await injectSession(page, 'shambhu', 'Shambhu');\n  147 |     await page.goto(BASE_URL);\n> 148 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 8000 });\n      |                                           ^\n  149 |     // Simulate network drop after page loads — UI should not crash\n  150 |     await context.setOffline(true);\n  151 |     await page.click('#nav-transfer');\n    at /var/www/360lm/tests/custodian.spec.js:148:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:06:36.782Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-361ba-n-network-drops-mid-session-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-361ba-n-network-drops-mid-session-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 148
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-f27288a8c4de1f261fad",
              "file": "custodian.spec.js",
              "line": 145,
              "column": 3
            }
          ]
        },
        {
          "title": "Fund Custodian — v4 Ledger Enhancements (HTML structure)",
          "file": "custodian.spec.js",
          "line": 160,
          "column": 6,
          "specs": [
            {
              "title": "sw cache version is current",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 122,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:165:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 165
                        },
                        "snippet": "  163 |     const resp = await ctx.get(BASE_URL);\n  164 |     const html = await resp.text();\n> 165 |     expect(html).toMatch(/custodian-v\\d+/);\n      |                  ^\n  166 |     await ctx.dispose();\n  167 |   });\n  168 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  163 |     const resp = await ctx.get(BASE_URL);\n  164 |     const html = await resp.text();\n> 165 |     expect(html).toMatch(/custodian-v\\d+/);\n      |                  ^\n  166 |     await ctx.dispose();\n  167 |   });\n  168 |\n    at /var/www/360lm/tests/custodian.spec.js:165:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:07:11.904Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6178e-sw-cache-version-is-current-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 123,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:165:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 165
                        },
                        "snippet": "  163 |     const resp = await ctx.get(BASE_URL);\n  164 |     const html = await resp.text();\n> 165 |     expect(html).toMatch(/custodian-v\\d+/);\n      |                  ^\n  166 |     await ctx.dispose();\n  167 |   });\n  168 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  163 |     const resp = await ctx.get(BASE_URL);\n  164 |     const html = await resp.text();\n> 165 |     expect(html).toMatch(/custodian-v\\d+/);\n      |                  ^\n  166 |     await ctx.dispose();\n  167 |   });\n  168 |\n    at /var/www/360lm/tests/custodian.spec.js:165:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:07:12.790Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6178e-sw-cache-version-is-current-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-f4824854ca168f7c0608",
              "file": "custodian.spec.js",
              "line": 161,
              "column": 3
            },
            {
              "title": "Account tab present in ledger",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 124,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:173:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 173
                        },
                        "snippet": "  171 |     const resp = await ctx.get(BASE_URL);\n  172 |     const html = await resp.text();\n> 173 |     expect(html).toContain('id=\"tab-account\"');\n      |                  ^\n  174 |     expect(html).toContain(\"switchLedgerTab('account')\");\n  175 |     expect(html).toContain('id=\"ledger-account-panel\"');\n  176 |     expect(html).toContain('id=\"account-list\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 173
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  171 |     const resp = await ctx.get(BASE_URL);\n  172 |     const html = await resp.text();\n> 173 |     expect(html).toContain('id=\"tab-account\"');\n      |                  ^\n  174 |     expect(html).toContain(\"switchLedgerTab('account')\");\n  175 |     expect(html).toContain('id=\"ledger-account-panel\"');\n  176 |     expect(html).toContain('id=\"account-list\"');\n    at /var/www/360lm/tests/custodian.spec.js:173:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:07:13.634Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09507-count-tab-present-in-ledger-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 173
                      }
                    },
                    {
                      "workerIndex": 125,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:173:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 173
                        },
                        "snippet": "  171 |     const resp = await ctx.get(BASE_URL);\n  172 |     const html = await resp.text();\n> 173 |     expect(html).toContain('id=\"tab-account\"');\n      |                  ^\n  174 |     expect(html).toContain(\"switchLedgerTab('account')\");\n  175 |     expect(html).toContain('id=\"ledger-account-panel\"');\n  176 |     expect(html).toContain('id=\"account-list\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 173
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  171 |     const resp = await ctx.get(BASE_URL);\n  172 |     const html = await resp.text();\n> 173 |     expect(html).toContain('id=\"tab-account\"');\n      |                  ^\n  174 |     expect(html).toContain(\"switchLedgerTab('account')\");\n  175 |     expect(html).toContain('id=\"ledger-account-panel\"');\n  176 |     expect(html).toContain('id=\"account-list\"');\n    at /var/www/360lm/tests/custodian.spec.js:173:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:07:14.521Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09507-count-tab-present-in-ledger-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 173
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-9a5901198de9c9bbf9cf",
              "file": "custodian.spec.js",
              "line": 169,
              "column": 3
            },
            {
              "title": "sort toolbar and filter button present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 126,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 50,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:184:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 184
                        },
                        "snippet": "  182 |     const resp = await ctx.get(BASE_URL);\n  183 |     const html = await resp.text();\n> 184 |     expect(html).toContain('id=\"sort-desc\"');\n      |                  ^\n  185 |     expect(html).toContain('id=\"sort-asc\"');\n  186 |     expect(html).toContain('id=\"ledger-filter-btn\"');\n  187 |     expect(html).toContain('setLedgerSort');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 184
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  182 |     const resp = await ctx.get(BASE_URL);\n  183 |     const html = await resp.text();\n> 184 |     expect(html).toContain('id=\"sort-desc\"');\n      |                  ^\n  185 |     expect(html).toContain('id=\"sort-asc\"');\n  186 |     expect(html).toContain('id=\"ledger-filter-btn\"');\n  187 |     expect(html).toContain('setLedgerSort');\n    at /var/www/360lm/tests/custodian.spec.js:184:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:07:15.471Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6114e-r-and-filter-button-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 184
                      }
                    },
                    {
                      "workerIndex": 127,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:184:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 184
                        },
                        "snippet": "  182 |     const resp = await ctx.get(BASE_URL);\n  183 |     const html = await resp.text();\n> 184 |     expect(html).toContain('id=\"sort-desc\"');\n      |                  ^\n  185 |     expect(html).toContain('id=\"sort-asc\"');\n  186 |     expect(html).toContain('id=\"ledger-filter-btn\"');\n  187 |     expect(html).toContain('setLedgerSort');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 184
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  182 |     const resp = await ctx.get(BASE_URL);\n  183 |     const html = await resp.text();\n> 184 |     expect(html).toContain('id=\"sort-desc\"');\n      |                  ^\n  185 |     expect(html).toContain('id=\"sort-asc\"');\n  186 |     expect(html).toContain('id=\"ledger-filter-btn\"');\n  187 |     expect(html).toContain('setLedgerSort');\n    at /var/www/360lm/tests/custodian.spec.js:184:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:07:16.287Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6114e-r-and-filter-button-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 184
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-32be9b1f629139dc1f8f",
              "file": "custodian.spec.js",
              "line": 180,
              "column": 3
            },
            {
              "title": "filter sheet HTML present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 128,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:196:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 196
                        },
                        "snippet": "  194 |     const resp = await ctx.get(BASE_URL);\n  195 |     const html = await resp.text();\n> 196 |     expect(html).toContain('id=\"filter-overlay\"');\n      |                  ^\n  197 |     expect(html).toContain('id=\"flt-party-chips\"');\n  198 |     expect(html).toContain('id=\"flt-mode-chips\"');\n  199 |     expect(html).toContain('id=\"flt-status-chips\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 196
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  194 |     const resp = await ctx.get(BASE_URL);\n  195 |     const html = await resp.text();\n> 196 |     expect(html).toContain('id=\"filter-overlay\"');\n      |                  ^\n  197 |     expect(html).toContain('id=\"flt-party-chips\"');\n  198 |     expect(html).toContain('id=\"flt-mode-chips\"');\n  199 |     expect(html).toContain('id=\"flt-status-chips\"');\n    at /var/www/360lm/tests/custodian.spec.js:196:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:07:17.146Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-a19bb-e-filter-sheet-HTML-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 196
                      }
                    },
                    {
                      "workerIndex": 129,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:196:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 196
                        },
                        "snippet": "  194 |     const resp = await ctx.get(BASE_URL);\n  195 |     const html = await resp.text();\n> 196 |     expect(html).toContain('id=\"filter-overlay\"');\n      |                  ^\n  197 |     expect(html).toContain('id=\"flt-party-chips\"');\n  198 |     expect(html).toContain('id=\"flt-mode-chips\"');\n  199 |     expect(html).toContain('id=\"flt-status-chips\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 196
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  194 |     const resp = await ctx.get(BASE_URL);\n  195 |     const html = await resp.text();\n> 196 |     expect(html).toContain('id=\"filter-overlay\"');\n      |                  ^\n  197 |     expect(html).toContain('id=\"flt-party-chips\"');\n  198 |     expect(html).toContain('id=\"flt-mode-chips\"');\n  199 |     expect(html).toContain('id=\"flt-status-chips\"');\n    at /var/www/360lm/tests/custodian.spec.js:196:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:07:18.091Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-a19bb-e-filter-sheet-HTML-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 196
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-2535c93247e6c476d0ea",
              "file": "custodian.spec.js",
              "line": 192,
              "column": 3
            },
            {
              "title": "detail sheet HTML present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 130,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:210:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 210
                        },
                        "snippet": "  208 |     const resp = await ctx.get(BASE_URL);\n  209 |     const html = await resp.text();\n> 210 |     expect(html).toContain('id=\"detail-overlay\"');\n      |                  ^\n  211 |     expect(html).toContain('id=\"detail-title\"');\n  212 |     expect(html).toContain('id=\"detail-body\"');\n  213 |     expect(html).toContain('openDetailSheet');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 210
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  208 |     const resp = await ctx.get(BASE_URL);\n  209 |     const html = await resp.text();\n> 210 |     expect(html).toContain('id=\"detail-overlay\"');\n      |                  ^\n  211 |     expect(html).toContain('id=\"detail-title\"');\n  212 |     expect(html).toContain('id=\"detail-body\"');\n  213 |     expect(html).toContain('openDetailSheet');\n    at /var/www/360lm/tests/custodian.spec.js:210:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:07:19.094Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-ad8e1-e-detail-sheet-HTML-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 210
                      }
                    },
                    {
                      "workerIndex": 131,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:210:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 210
                        },
                        "snippet": "  208 |     const resp = await ctx.get(BASE_URL);\n  209 |     const html = await resp.text();\n> 210 |     expect(html).toContain('id=\"detail-overlay\"');\n      |                  ^\n  211 |     expect(html).toContain('id=\"detail-title\"');\n  212 |     expect(html).toContain('id=\"detail-body\"');\n  213 |     expect(html).toContain('openDetailSheet');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 210
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  208 |     const resp = await ctx.get(BASE_URL);\n  209 |     const html = await resp.text();\n> 210 |     expect(html).toContain('id=\"detail-overlay\"');\n      |                  ^\n  211 |     expect(html).toContain('id=\"detail-title\"');\n  212 |     expect(html).toContain('id=\"detail-body\"');\n  213 |     expect(html).toContain('openDetailSheet');\n    at /var/www/360lm/tests/custodian.spec.js:210:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:07:20.031Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-ad8e1-e-detail-sheet-HTML-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 210
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-f6f47432e918792e5dce",
              "file": "custodian.spec.js",
              "line": 206,
              "column": 3
            },
            {
              "title": "key ledger functions present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 132,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:222:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 222
                        },
                        "snippet": "  220 |     const resp = await ctx.get(BASE_URL);\n  221 |     const html = await resp.text();\n> 222 |     expect(html).toContain('function loadAccount');\n      |                  ^\n  223 |     expect(html).toContain('function renderAccountList');\n  224 |     expect(html).toContain('function acctRowHtml');\n  225 |     expect(html).toContain('function filterRows');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 222
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  220 |     const resp = await ctx.get(BASE_URL);\n  221 |     const html = await resp.text();\n> 222 |     expect(html).toContain('function loadAccount');\n      |                  ^\n  223 |     expect(html).toContain('function renderAccountList');\n  224 |     expect(html).toContain('function acctRowHtml');\n  225 |     expect(html).toContain('function filterRows');\n    at /var/www/360lm/tests/custodian.spec.js:222:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:07:21.058Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09707-ey-ledger-functions-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 133,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:222:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 222
                        },
                        "snippet": "  220 |     const resp = await ctx.get(BASE_URL);\n  221 |     const html = await resp.text();\n> 222 |     expect(html).toContain('function loadAccount');\n      |                  ^\n  223 |     expect(html).toContain('function renderAccountList');\n  224 |     expect(html).toContain('function acctRowHtml');\n  225 |     expect(html).toContain('function filterRows');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 222
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  220 |     const resp = await ctx.get(BASE_URL);\n  221 |     const html = await resp.text();\n> 222 |     expect(html).toContain('function loadAccount');\n      |                  ^\n  223 |     expect(html).toContain('function renderAccountList');\n  224 |     expect(html).toContain('function acctRowHtml');\n  225 |     expect(html).toContain('function filterRows');\n    at /var/www/360lm/tests/custodian.spec.js:222:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:07:21.919Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09707-ey-ledger-functions-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-d25f1624063b0e82cd70",
              "file": "custodian.spec.js",
              "line": 218,
              "column": 3
            },
            {
              "title": "Account tab loads and renders (Shambhu)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 134,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30098,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 236
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  234 |     await injectSession(page, 'shambhu', 'Shambhu');\n  235 |     await page.goto(BASE_URL);\n> 236 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  237 |     await page.locator('#tab-account').click();\n  238 |     await expect(page.locator('#ledger-account-panel')).toBeVisible({ timeout: 8000 });\n  239 |     // No data yet (clean DB) — should show empty state\n    at /var/www/360lm/tests/custodian.spec.js:236:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:07:22.801Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c892e--loads-and-renders-Shambhu--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c892e--loads-and-renders-Shambhu--android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 135,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30088,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 236
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  234 |     await injectSession(page, 'shambhu', 'Shambhu');\n  235 |     await page.goto(BASE_URL);\n> 236 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  237 |     await page.locator('#tab-account').click();\n  238 |     await expect(page.locator('#ledger-account-panel')).toBeVisible({ timeout: 8000 });\n  239 |     // No data yet (clean DB) — should show empty state\n    at /var/www/360lm/tests/custodian.spec.js:236:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:07:53.989Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c892e--loads-and-renders-Shambhu--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c892e--loads-and-renders-Shambhu--android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-de7595abf110a20208eb",
              "file": "custodian.spec.js",
              "line": 233,
              "column": 3
            },
            {
              "title": "sort buttons toggle active state",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 136,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 246
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  244 |     await injectSession(page, 'shambhu', 'Shambhu');\n  245 |     await page.goto(BASE_URL);\n> 246 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  247 |     await expect(page.locator('#sort-desc')).toHaveClass(/active/);\n  248 |     await page.locator('#sort-asc').click();\n  249 |     await expect(page.locator('#sort-asc')).toHaveClass(/active/);\n    at /var/www/360lm/tests/custodian.spec.js:246:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:08:25.104Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-78f72-buttons-toggle-active-state-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-78f72-buttons-toggle-active-state-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 137,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30094,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 246
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  244 |     await injectSession(page, 'shambhu', 'Shambhu');\n  245 |     await page.goto(BASE_URL);\n> 246 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  247 |     await expect(page.locator('#sort-desc')).toHaveClass(/active/);\n  248 |     await page.locator('#sort-asc').click();\n  249 |     await expect(page.locator('#sort-asc')).toHaveClass(/active/);\n    at /var/www/360lm/tests/custodian.spec.js:246:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:08:56.209Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-78f72-buttons-toggle-active-state-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-78f72-buttons-toggle-active-state-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-1ae6a1d2788fdc49c085",
              "file": "custodian.spec.js",
              "line": 243,
              "column": 3
            },
            {
              "title": "filter button opens filter sheet",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 138,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30080,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 256
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  254 |     await injectSession(page, 'shambhu', 'Shambhu');\n  255 |     await page.goto(BASE_URL);\n> 256 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  257 |     await page.locator('#ledger-filter-btn').click();\n  258 |     await expect(page.locator('#filter-overlay')).toHaveClass(/open/);\n  259 |     await page.locator('#filter-overlay .btm-close').click();\n    at /var/www/360lm/tests/custodian.spec.js:256:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:09:27.348Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c9a59-r-button-opens-filter-sheet-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c9a59-r-button-opens-filter-sheet-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 139,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 256
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  254 |     await injectSession(page, 'shambhu', 'Shambhu');\n  255 |     await page.goto(BASE_URL);\n> 256 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  257 |     await page.locator('#ledger-filter-btn').click();\n  258 |     await expect(page.locator('#filter-overlay')).toHaveClass(/open/);\n  259 |     await page.locator('#filter-overlay .btm-close').click();\n    at /var/www/360lm/tests/custodian.spec.js:256:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:09:58.514Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c9a59-r-button-opens-filter-sheet-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c9a59-r-button-opens-filter-sheet-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-a52ec8a50e063f030d8a",
              "file": "custodian.spec.js",
              "line": 253,
              "column": 3
            },
            {
              "title": "sw cache version is current",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1129,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 51,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:165:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 165
                        },
                        "snippet": "  163 |     const resp = await ctx.get(BASE_URL);\n  164 |     const html = await resp.text();\n> 165 |     expect(html).toMatch(/custodian-v\\d+/);\n      |                  ^\n  166 |     await ctx.dispose();\n  167 |   });\n  168 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  163 |     const resp = await ctx.get(BASE_URL);\n  164 |     const html = await resp.text();\n> 165 |     expect(html).toMatch(/custodian-v\\d+/);\n      |                  ^\n  166 |     await ctx.dispose();\n  167 |   });\n  168 |\n    at /var/www/360lm/tests/custodian.spec.js:165:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:06:45.930Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6178e-sw-cache-version-is-current-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 1130,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:165:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 165
                        },
                        "snippet": "  163 |     const resp = await ctx.get(BASE_URL);\n  164 |     const html = await resp.text();\n> 165 |     expect(html).toMatch(/custodian-v\\d+/);\n      |                  ^\n  166 |     await ctx.dispose();\n  167 |   });\n  168 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/custodian-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  163 |     const resp = await ctx.get(BASE_URL);\n  164 |     const html = await resp.text();\n> 165 |     expect(html).toMatch(/custodian-v\\d+/);\n      |                  ^\n  166 |     await ctx.dispose();\n  167 |   });\n  168 |\n    at /var/www/360lm/tests/custodian.spec.js:165:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:06:46.760Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6178e-sw-cache-version-is-current-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-c9977170903a002f619c",
              "file": "custodian.spec.js",
              "line": 161,
              "column": 3
            },
            {
              "title": "Account tab present in ledger",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1131,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:173:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 173
                        },
                        "snippet": "  171 |     const resp = await ctx.get(BASE_URL);\n  172 |     const html = await resp.text();\n> 173 |     expect(html).toContain('id=\"tab-account\"');\n      |                  ^\n  174 |     expect(html).toContain(\"switchLedgerTab('account')\");\n  175 |     expect(html).toContain('id=\"ledger-account-panel\"');\n  176 |     expect(html).toContain('id=\"account-list\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 173
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  171 |     const resp = await ctx.get(BASE_URL);\n  172 |     const html = await resp.text();\n> 173 |     expect(html).toContain('id=\"tab-account\"');\n      |                  ^\n  174 |     expect(html).toContain(\"switchLedgerTab('account')\");\n  175 |     expect(html).toContain('id=\"ledger-account-panel\"');\n  176 |     expect(html).toContain('id=\"account-list\"');\n    at /var/www/360lm/tests/custodian.spec.js:173:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:06:47.650Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09507-count-tab-present-in-ledger-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 173
                      }
                    },
                    {
                      "workerIndex": 1132,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:173:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 173
                        },
                        "snippet": "  171 |     const resp = await ctx.get(BASE_URL);\n  172 |     const html = await resp.text();\n> 173 |     expect(html).toContain('id=\"tab-account\"');\n      |                  ^\n  174 |     expect(html).toContain(\"switchLedgerTab('account')\");\n  175 |     expect(html).toContain('id=\"ledger-account-panel\"');\n  176 |     expect(html).toContain('id=\"account-list\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 173
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tab-account\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  171 |     const resp = await ctx.get(BASE_URL);\n  172 |     const html = await resp.text();\n> 173 |     expect(html).toContain('id=\"tab-account\"');\n      |                  ^\n  174 |     expect(html).toContain(\"switchLedgerTab('account')\");\n  175 |     expect(html).toContain('id=\"ledger-account-panel\"');\n  176 |     expect(html).toContain('id=\"account-list\"');\n    at /var/www/360lm/tests/custodian.spec.js:173:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:06:48.542Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09507-count-tab-present-in-ledger-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 173
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-2dd9336ff55b63091957",
              "file": "custodian.spec.js",
              "line": 169,
              "column": 3
            },
            {
              "title": "sort toolbar and filter button present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1133,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:184:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 184
                        },
                        "snippet": "  182 |     const resp = await ctx.get(BASE_URL);\n  183 |     const html = await resp.text();\n> 184 |     expect(html).toContain('id=\"sort-desc\"');\n      |                  ^\n  185 |     expect(html).toContain('id=\"sort-asc\"');\n  186 |     expect(html).toContain('id=\"ledger-filter-btn\"');\n  187 |     expect(html).toContain('setLedgerSort');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 184
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  182 |     const resp = await ctx.get(BASE_URL);\n  183 |     const html = await resp.text();\n> 184 |     expect(html).toContain('id=\"sort-desc\"');\n      |                  ^\n  185 |     expect(html).toContain('id=\"sort-asc\"');\n  186 |     expect(html).toContain('id=\"ledger-filter-btn\"');\n  187 |     expect(html).toContain('setLedgerSort');\n    at /var/www/360lm/tests/custodian.spec.js:184:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:06:49.398Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6114e-r-and-filter-button-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 184
                      }
                    },
                    {
                      "workerIndex": 1134,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:184:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 184
                        },
                        "snippet": "  182 |     const resp = await ctx.get(BASE_URL);\n  183 |     const html = await resp.text();\n> 184 |     expect(html).toContain('id=\"sort-desc\"');\n      |                  ^\n  185 |     expect(html).toContain('id=\"sort-asc\"');\n  186 |     expect(html).toContain('id=\"ledger-filter-btn\"');\n  187 |     expect(html).toContain('setLedgerSort');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 184
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"sort-desc\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  182 |     const resp = await ctx.get(BASE_URL);\n  183 |     const html = await resp.text();\n> 184 |     expect(html).toContain('id=\"sort-desc\"');\n      |                  ^\n  185 |     expect(html).toContain('id=\"sort-asc\"');\n  186 |     expect(html).toContain('id=\"ledger-filter-btn\"');\n  187 |     expect(html).toContain('setLedgerSort');\n    at /var/www/360lm/tests/custodian.spec.js:184:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:06:50.249Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6114e-r-and-filter-button-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 184
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-d448ad7d05c716b3caa9",
              "file": "custodian.spec.js",
              "line": 180,
              "column": 3
            },
            {
              "title": "filter sheet HTML present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1135,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:196:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 196
                        },
                        "snippet": "  194 |     const resp = await ctx.get(BASE_URL);\n  195 |     const html = await resp.text();\n> 196 |     expect(html).toContain('id=\"filter-overlay\"');\n      |                  ^\n  197 |     expect(html).toContain('id=\"flt-party-chips\"');\n  198 |     expect(html).toContain('id=\"flt-mode-chips\"');\n  199 |     expect(html).toContain('id=\"flt-status-chips\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 196
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  194 |     const resp = await ctx.get(BASE_URL);\n  195 |     const html = await resp.text();\n> 196 |     expect(html).toContain('id=\"filter-overlay\"');\n      |                  ^\n  197 |     expect(html).toContain('id=\"flt-party-chips\"');\n  198 |     expect(html).toContain('id=\"flt-mode-chips\"');\n  199 |     expect(html).toContain('id=\"flt-status-chips\"');\n    at /var/www/360lm/tests/custodian.spec.js:196:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:06:51.162Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-a19bb-e-filter-sheet-HTML-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 196
                      }
                    },
                    {
                      "workerIndex": 1136,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:196:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 196
                        },
                        "snippet": "  194 |     const resp = await ctx.get(BASE_URL);\n  195 |     const html = await resp.text();\n> 196 |     expect(html).toContain('id=\"filter-overlay\"');\n      |                  ^\n  197 |     expect(html).toContain('id=\"flt-party-chips\"');\n  198 |     expect(html).toContain('id=\"flt-mode-chips\"');\n  199 |     expect(html).toContain('id=\"flt-status-chips\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 196
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"filter-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  194 |     const resp = await ctx.get(BASE_URL);\n  195 |     const html = await resp.text();\n> 196 |     expect(html).toContain('id=\"filter-overlay\"');\n      |                  ^\n  197 |     expect(html).toContain('id=\"flt-party-chips\"');\n  198 |     expect(html).toContain('id=\"flt-mode-chips\"');\n  199 |     expect(html).toContain('id=\"flt-status-chips\"');\n    at /var/www/360lm/tests/custodian.spec.js:196:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:06:52.086Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-a19bb-e-filter-sheet-HTML-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 196
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-70ea16515e8f7579ebd2",
              "file": "custodian.spec.js",
              "line": 192,
              "column": 3
            },
            {
              "title": "detail sheet HTML present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1137,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:210:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 210
                        },
                        "snippet": "  208 |     const resp = await ctx.get(BASE_URL);\n  209 |     const html = await resp.text();\n> 210 |     expect(html).toContain('id=\"detail-overlay\"');\n      |                  ^\n  211 |     expect(html).toContain('id=\"detail-title\"');\n  212 |     expect(html).toContain('id=\"detail-body\"');\n  213 |     expect(html).toContain('openDetailSheet');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 210
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  208 |     const resp = await ctx.get(BASE_URL);\n  209 |     const html = await resp.text();\n> 210 |     expect(html).toContain('id=\"detail-overlay\"');\n      |                  ^\n  211 |     expect(html).toContain('id=\"detail-title\"');\n  212 |     expect(html).toContain('id=\"detail-body\"');\n  213 |     expect(html).toContain('openDetailSheet');\n    at /var/www/360lm/tests/custodian.spec.js:210:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:06:52.979Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-ad8e1-e-detail-sheet-HTML-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 210
                      }
                    },
                    {
                      "workerIndex": 1138,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:210:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 210
                        },
                        "snippet": "  208 |     const resp = await ctx.get(BASE_URL);\n  209 |     const html = await resp.text();\n> 210 |     expect(html).toContain('id=\"detail-overlay\"');\n      |                  ^\n  211 |     expect(html).toContain('id=\"detail-title\"');\n  212 |     expect(html).toContain('id=\"detail-body\"');\n  213 |     expect(html).toContain('openDetailSheet');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 210
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"detail-overlay\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  208 |     const resp = await ctx.get(BASE_URL);\n  209 |     const html = await resp.text();\n> 210 |     expect(html).toContain('id=\"detail-overlay\"');\n      |                  ^\n  211 |     expect(html).toContain('id=\"detail-title\"');\n  212 |     expect(html).toContain('id=\"detail-body\"');\n  213 |     expect(html).toContain('openDetailSheet');\n    at /var/www/360lm/tests/custodian.spec.js:210:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:06:53.889Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-ad8e1-e-detail-sheet-HTML-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 210
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-8d0f15494294c91a2129",
              "file": "custodian.spec.js",
              "line": 206,
              "column": 3
            },
            {
              "title": "key ledger functions present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1139,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:222:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 222
                        },
                        "snippet": "  220 |     const resp = await ctx.get(BASE_URL);\n  221 |     const html = await resp.text();\n> 222 |     expect(html).toContain('function loadAccount');\n      |                  ^\n  223 |     expect(html).toContain('function renderAccountList');\n  224 |     expect(html).toContain('function acctRowHtml');\n  225 |     expect(html).toContain('function filterRows');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 222
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  220 |     const resp = await ctx.get(BASE_URL);\n  221 |     const html = await resp.text();\n> 222 |     expect(html).toContain('function loadAccount');\n      |                  ^\n  223 |     expect(html).toContain('function renderAccountList');\n  224 |     expect(html).toContain('function acctRowHtml');\n  225 |     expect(html).toContain('function filterRows');\n    at /var/www/360lm/tests/custodian.spec.js:222:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:06:54.725Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09707-ey-ledger-functions-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1140,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 51,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:222:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 222
                        },
                        "snippet": "  220 |     const resp = await ctx.get(BASE_URL);\n  221 |     const html = await resp.text();\n> 222 |     expect(html).toContain('function loadAccount');\n      |                  ^\n  223 |     expect(html).toContain('function renderAccountList');\n  224 |     expect(html).toContain('function acctRowHtml');\n  225 |     expect(html).toContain('function filterRows');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 222
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function loadAccount\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  220 |     const resp = await ctx.get(BASE_URL);\n  221 |     const html = await resp.text();\n> 222 |     expect(html).toContain('function loadAccount');\n      |                  ^\n  223 |     expect(html).toContain('function renderAccountList');\n  224 |     expect(html).toContain('function acctRowHtml');\n  225 |     expect(html).toContain('function filterRows');\n    at /var/www/360lm/tests/custodian.spec.js:222:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:06:55.565Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-09707-ey-ledger-functions-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-4bf5606ec5879ca1d775",
              "file": "custodian.spec.js",
              "line": 218,
              "column": 3
            },
            {
              "title": "Account tab loads and renders (Shambhu)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1141,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30096,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 236
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  234 |     await injectSession(page, 'shambhu', 'Shambhu');\n  235 |     await page.goto(BASE_URL);\n> 236 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  237 |     await page.locator('#tab-account').click();\n  238 |     await expect(page.locator('#ledger-account-panel')).toBeVisible({ timeout: 8000 });\n  239 |     // No data yet (clean DB) — should show empty state\n    at /var/www/360lm/tests/custodian.spec.js:236:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:06:56.419Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c892e--loads-and-renders-Shambhu--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c892e--loads-and-renders-Shambhu--desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1142,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30068,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 236
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  234 |     await injectSession(page, 'shambhu', 'Shambhu');\n  235 |     await page.goto(BASE_URL);\n> 236 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  237 |     await page.locator('#tab-account').click();\n  238 |     await expect(page.locator('#ledger-account-panel')).toBeVisible({ timeout: 8000 });\n  239 |     // No data yet (clean DB) — should show empty state\n    at /var/www/360lm/tests/custodian.spec.js:236:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:07:27.496Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c892e--loads-and-renders-Shambhu--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c892e--loads-and-renders-Shambhu--desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-1befb120c95b0e5bec2f",
              "file": "custodian.spec.js",
              "line": 233,
              "column": 3
            },
            {
              "title": "sort buttons toggle active state",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1143,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30098,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 246
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  244 |     await injectSession(page, 'shambhu', 'Shambhu');\n  245 |     await page.goto(BASE_URL);\n> 246 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  247 |     await expect(page.locator('#sort-desc')).toHaveClass(/active/);\n  248 |     await page.locator('#sort-asc').click();\n  249 |     await expect(page.locator('#sort-asc')).toHaveClass(/active/);\n    at /var/www/360lm/tests/custodian.spec.js:246:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:07:58.572Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-78f72-buttons-toggle-active-state-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-78f72-buttons-toggle-active-state-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1144,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30098,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 246
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  244 |     await injectSession(page, 'shambhu', 'Shambhu');\n  245 |     await page.goto(BASE_URL);\n> 246 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  247 |     await expect(page.locator('#sort-desc')).toHaveClass(/active/);\n  248 |     await page.locator('#sort-asc').click();\n  249 |     await expect(page.locator('#sort-asc')).toHaveClass(/active/);\n    at /var/www/360lm/tests/custodian.spec.js:246:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:08:29.603Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-78f72-buttons-toggle-active-state-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-78f72-buttons-toggle-active-state-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-1d9cb22d0aa275ad62d6",
              "file": "custodian.spec.js",
              "line": 243,
              "column": 3
            },
            {
              "title": "filter button opens filter sheet",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1145,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30090,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 256
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  254 |     await injectSession(page, 'shambhu', 'Shambhu');\n  255 |     await page.goto(BASE_URL);\n> 256 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  257 |     await page.locator('#ledger-filter-btn').click();\n  258 |     await expect(page.locator('#filter-overlay')).toHaveClass(/open/);\n  259 |     await page.locator('#filter-overlay .btm-close').click();\n    at /var/www/360lm/tests/custodian.spec.js:256:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:09:01.250Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c9a59-r-button-opens-filter-sheet-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c9a59-r-button-opens-filter-sheet-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1146,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30330,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 39,
                            "line": 256
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  254 |     await injectSession(page, 'shambhu', 'Shambhu');\n  255 |     await page.goto(BASE_URL);\n> 256 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  257 |     await page.locator('#ledger-filter-btn').click();\n  258 |     await expect(page.locator('#filter-overlay')).toHaveClass(/open/);\n  259 |     await page.locator('#filter-overlay .btm-close').click();\n    at /var/www/360lm/tests/custodian.spec.js:256:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:09:32.518Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c9a59-r-button-opens-filter-sheet-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-c9a59-r-button-opens-filter-sheet-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-2f95315b77f296406a8e",
              "file": "custodian.spec.js",
              "line": 253,
              "column": 3
            }
          ]
        },
        {
          "title": "Fund Custodian — Phase 3b Line Items (HTML structure)",
          "file": "custodian.spec.js",
          "line": 265,
          "column": 6,
          "specs": [
            {
              "title": "line-items section + helpers present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 140,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:270:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 270
                        },
                        "snippet": "  268 |     const resp = await ctx.get(BASE_URL);\n  269 |     const html = await resp.text();\n> 270 |     expect(html).toContain('id=\"tf-lines-section\"');\n      |                  ^\n  271 |     expect(html).toContain('id=\"tf-lines-list\"');\n  272 |     expect(html).toContain('id=\"tf-lines-sum\"');\n  273 |     expect(html).toContain('function addEmptyLine');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 270
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  268 |     const resp = await ctx.get(BASE_URL);\n  269 |     const html = await resp.text();\n> 270 |     expect(html).toContain('id=\"tf-lines-section\"');\n      |                  ^\n  271 |     expect(html).toContain('id=\"tf-lines-list\"');\n  272 |     expect(html).toContain('id=\"tf-lines-sum\"');\n  273 |     expect(html).toContain('function addEmptyLine');\n    at /var/www/360lm/tests/custodian.spec.js:270:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:10:29.629Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-79fcb-ems-section-helpers-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 270
                      }
                    },
                    {
                      "workerIndex": 141,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:270:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 270
                        },
                        "snippet": "  268 |     const resp = await ctx.get(BASE_URL);\n  269 |     const html = await resp.text();\n> 270 |     expect(html).toContain('id=\"tf-lines-section\"');\n      |                  ^\n  271 |     expect(html).toContain('id=\"tf-lines-list\"');\n  272 |     expect(html).toContain('id=\"tf-lines-sum\"');\n  273 |     expect(html).toContain('function addEmptyLine');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 270
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  268 |     const resp = await ctx.get(BASE_URL);\n  269 |     const html = await resp.text();\n> 270 |     expect(html).toContain('id=\"tf-lines-section\"');\n      |                  ^\n  271 |     expect(html).toContain('id=\"tf-lines-list\"');\n  272 |     expect(html).toContain('id=\"tf-lines-sum\"');\n  273 |     expect(html).toContain('function addEmptyLine');\n    at /var/www/360lm/tests/custodian.spec.js:270:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:10:30.549Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-79fcb-ems-section-helpers-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 270
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-0836f12890ad706f3a87",
              "file": "custodian.spec.js",
              "line": 266,
              "column": 3
            },
            {
              "title": "CSS for line items present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 142,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:286:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 286
                        },
                        "snippet": "  284 |     const resp = await ctx.get(BASE_URL);\n  285 |     const html = await resp.text();\n> 286 |     expect(html).toContain('.li-row');\n      |                  ^\n  287 |     expect(html).toContain('.li-prod-drop');\n  288 |     expect(html).toContain('.li-quick-add');\n  289 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 286
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  284 |     const resp = await ctx.get(BASE_URL);\n  285 |     const html = await resp.text();\n> 286 |     expect(html).toContain('.li-row');\n      |                  ^\n  287 |     expect(html).toContain('.li-prod-drop');\n  288 |     expect(html).toContain('.li-quick-add');\n  289 |     await ctx.dispose();\n    at /var/www/360lm/tests/custodian.spec.js:286:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:10:31.443Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-ff2dc--CSS-for-line-items-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 286
                      }
                    },
                    {
                      "workerIndex": 143,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:286:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 286
                        },
                        "snippet": "  284 |     const resp = await ctx.get(BASE_URL);\n  285 |     const html = await resp.text();\n> 286 |     expect(html).toContain('.li-row');\n      |                  ^\n  287 |     expect(html).toContain('.li-prod-drop');\n  288 |     expect(html).toContain('.li-quick-add');\n  289 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 286
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  284 |     const resp = await ctx.get(BASE_URL);\n  285 |     const html = await resp.text();\n> 286 |     expect(html).toContain('.li-row');\n      |                  ^\n  287 |     expect(html).toContain('.li-prod-drop');\n  288 |     expect(html).toContain('.li-quick-add');\n  289 |     await ctx.dispose();\n    at /var/www/360lm/tests/custodian.spec.js:286:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:10:32.420Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-ff2dc--CSS-for-line-items-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 286
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-c505c2ae5ae0e7ebc68b",
              "file": "custodian.spec.js",
              "line": 282,
              "column": 3
            },
            {
              "title": "line-items section + helpers present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1147,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 85,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:270:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 270
                        },
                        "snippet": "  268 |     const resp = await ctx.get(BASE_URL);\n  269 |     const html = await resp.text();\n> 270 |     expect(html).toContain('id=\"tf-lines-section\"');\n      |                  ^\n  271 |     expect(html).toContain('id=\"tf-lines-list\"');\n  272 |     expect(html).toContain('id=\"tf-lines-sum\"');\n  273 |     expect(html).toContain('function addEmptyLine');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 270
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  268 |     const resp = await ctx.get(BASE_URL);\n  269 |     const html = await resp.text();\n> 270 |     expect(html).toContain('id=\"tf-lines-section\"');\n      |                  ^\n  271 |     expect(html).toContain('id=\"tf-lines-list\"');\n  272 |     expect(html).toContain('id=\"tf-lines-sum\"');\n  273 |     expect(html).toContain('function addEmptyLine');\n    at /var/www/360lm/tests/custodian.spec.js:270:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:05.592Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-79fcb-ems-section-helpers-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 270
                      }
                    },
                    {
                      "workerIndex": 1148,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:270:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 270
                        },
                        "snippet": "  268 |     const resp = await ctx.get(BASE_URL);\n  269 |     const html = await resp.text();\n> 270 |     expect(html).toContain('id=\"tf-lines-section\"');\n      |                  ^\n  271 |     expect(html).toContain('id=\"tf-lines-list\"');\n  272 |     expect(html).toContain('id=\"tf-lines-sum\"');\n  273 |     expect(html).toContain('function addEmptyLine');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 270
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-lines-section\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  268 |     const resp = await ctx.get(BASE_URL);\n  269 |     const html = await resp.text();\n> 270 |     expect(html).toContain('id=\"tf-lines-section\"');\n      |                  ^\n  271 |     expect(html).toContain('id=\"tf-lines-list\"');\n  272 |     expect(html).toContain('id=\"tf-lines-sum\"');\n  273 |     expect(html).toContain('function addEmptyLine');\n    at /var/www/360lm/tests/custodian.spec.js:270:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:10:06.794Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-79fcb-ems-section-helpers-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 270
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-5c858670c9c19b953c1a",
              "file": "custodian.spec.js",
              "line": 266,
              "column": 3
            },
            {
              "title": "CSS for line items present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1149,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 72,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:286:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 286
                        },
                        "snippet": "  284 |     const resp = await ctx.get(BASE_URL);\n  285 |     const html = await resp.text();\n> 286 |     expect(html).toContain('.li-row');\n      |                  ^\n  287 |     expect(html).toContain('.li-prod-drop');\n  288 |     expect(html).toContain('.li-quick-add');\n  289 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 286
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  284 |     const resp = await ctx.get(BASE_URL);\n  285 |     const html = await resp.text();\n> 286 |     expect(html).toContain('.li-row');\n      |                  ^\n  287 |     expect(html).toContain('.li-prod-drop');\n  288 |     expect(html).toContain('.li-quick-add');\n  289 |     await ctx.dispose();\n    at /var/www/360lm/tests/custodian.spec.js:286:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:07.805Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-ff2dc--CSS-for-line-items-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 286
                      }
                    },
                    {
                      "workerIndex": 1150,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:286:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 286
                        },
                        "snippet": "  284 |     const resp = await ctx.get(BASE_URL);\n  285 |     const html = await resp.text();\n> 286 |     expect(html).toContain('.li-row');\n      |                  ^\n  287 |     expect(html).toContain('.li-prod-drop');\n  288 |     expect(html).toContain('.li-quick-add');\n  289 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 286
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".li-row\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  284 |     const resp = await ctx.get(BASE_URL);\n  285 |     const html = await resp.text();\n> 286 |     expect(html).toContain('.li-row');\n      |                  ^\n  287 |     expect(html).toContain('.li-prod-drop');\n  288 |     expect(html).toContain('.li-quick-add');\n  289 |     await ctx.dispose();\n    at /var/www/360lm/tests/custodian.spec.js:286:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:10:08.833Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-ff2dc--CSS-for-line-items-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 286
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-3f96b5756054149977f0",
              "file": "custodian.spec.js",
              "line": 282,
              "column": 3
            }
          ]
        },
        {
          "title": "Fund Custodian — v12 Dynamic Custodians (HTML structure)",
          "file": "custodian.spec.js",
          "line": 294,
          "column": 6,
          "specs": [
            {
              "title": "hardcoded ADMIN_IDS and CUSTODIAN_IDS arrays are removed",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 144,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 58,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:10:33.339Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e1e49def2648bf8aa609-2985814aca2c74265f35",
              "file": "custodian.spec.js",
              "line": 295,
              "column": 3
            },
            {
              "title": "DB-driven auth — _myProfile and _isLimitedMode state vars present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 144,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 15,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:308:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 308
                        },
                        "snippet": "  306 |     const resp = await ctx.get(BASE_URL);\n  307 |     const html = await resp.text();\n> 308 |     expect(html).toContain('_myProfile');\n      |                  ^\n  309 |     expect(html).toContain('_isLimitedMode');\n  310 |     expect(html).toContain('profiles?custodian_id=eq.');\n  311 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 308
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  306 |     const resp = await ctx.get(BASE_URL);\n  307 |     const html = await resp.text();\n> 308 |     expect(html).toContain('_myProfile');\n      |                  ^\n  309 |     expect(html).toContain('_isLimitedMode');\n  310 |     expect(html).toContain('profiles?custodian_id=eq.');\n  311 |     await ctx.dispose();\n    at /var/www/360lm/tests/custodian.spec.js:308:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:10:33.449Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6d889-itedMode-state-vars-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 308
                      }
                    },
                    {
                      "workerIndex": 145,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:308:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 308
                        },
                        "snippet": "  306 |     const resp = await ctx.get(BASE_URL);\n  307 |     const html = await resp.text();\n> 308 |     expect(html).toContain('_myProfile');\n      |                  ^\n  309 |     expect(html).toContain('_isLimitedMode');\n  310 |     expect(html).toContain('profiles?custodian_id=eq.');\n  311 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 308
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  306 |     const resp = await ctx.get(BASE_URL);\n  307 |     const html = await resp.text();\n> 308 |     expect(html).toContain('_myProfile');\n      |                  ^\n  309 |     expect(html).toContain('_isLimitedMode');\n  310 |     expect(html).toContain('profiles?custodian_id=eq.');\n  311 |     await ctx.dispose();\n    at /var/www/360lm/tests/custodian.spec.js:308:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:10:34.267Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6d889-itedMode-state-vars-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 308
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-30a6d725d430c4cacdd3",
              "file": "custodian.spec.js",
              "line": 304,
              "column": 3
            },
            {
              "title": "deactivated-custodian \"Access Removed\" screen markup present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 146,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:318:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 318
                        },
                        "snippet": "  316 |     const resp = await ctx.get(BASE_URL);\n  317 |     const html = await resp.text();\n> 318 |     expect(html).toContain('Access Removed');\n      |                  ^\n  319 |     expect(html).toContain('Your custodian access has been deactivated');\n  320 |     await ctx.dispose();\n  321 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 318
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  316 |     const resp = await ctx.get(BASE_URL);\n  317 |     const html = await resp.text();\n> 318 |     expect(html).toContain('Access Removed');\n      |                  ^\n  319 |     expect(html).toContain('Your custodian access has been deactivated');\n  320 |     await ctx.dispose();\n  321 |   });\n    at /var/www/360lm/tests/custodian.spec.js:318:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:10:35.120Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-e46ed-moved-screen-markup-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 318
                      }
                    },
                    {
                      "workerIndex": 147,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:318:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 318
                        },
                        "snippet": "  316 |     const resp = await ctx.get(BASE_URL);\n  317 |     const html = await resp.text();\n> 318 |     expect(html).toContain('Access Removed');\n      |                  ^\n  319 |     expect(html).toContain('Your custodian access has been deactivated');\n  320 |     await ctx.dispose();\n  321 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 318
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  316 |     const resp = await ctx.get(BASE_URL);\n  317 |     const html = await resp.text();\n> 318 |     expect(html).toContain('Access Removed');\n      |                  ^\n  319 |     expect(html).toContain('Your custodian access has been deactivated');\n  320 |     await ctx.dispose();\n  321 |   });\n    at /var/www/360lm/tests/custodian.spec.js:318:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:10:35.984Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-e46ed-moved-screen-markup-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 318
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-b73c55a2fa175f9d6c87",
              "file": "custodian.spec.js",
              "line": 314,
              "column": 3
            },
            {
              "title": "manage screen has deactivate / reactivate / rename / admin functions",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 148,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:327:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 327
                        },
                        "snippet": "  325 |     const resp = await ctx.get(BASE_URL);\n  326 |     const html = await resp.text();\n> 327 |     expect(html).toContain('openDeactivateDialog');\n      |                  ^\n  328 |     expect(html).toContain('openReactivateDialog');\n  329 |     expect(html).toContain('openRenameDialog');\n  330 |     expect(html).toContain('openAdminDialog');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 327
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  325 |     const resp = await ctx.get(BASE_URL);\n  326 |     const html = await resp.text();\n> 327 |     expect(html).toContain('openDeactivateDialog');\n      |                  ^\n  328 |     expect(html).toContain('openReactivateDialog');\n  329 |     expect(html).toContain('openRenameDialog');\n  330 |     expect(html).toContain('openAdminDialog');\n    at /var/www/360lm/tests/custodian.spec.js:327:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:10:37.000Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-8f2b2-vate-rename-admin-functions-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 327
                      }
                    },
                    {
                      "workerIndex": 149,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:327:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 327
                        },
                        "snippet": "  325 |     const resp = await ctx.get(BASE_URL);\n  326 |     const html = await resp.text();\n> 327 |     expect(html).toContain('openDeactivateDialog');\n      |                  ^\n  328 |     expect(html).toContain('openReactivateDialog');\n  329 |     expect(html).toContain('openRenameDialog');\n  330 |     expect(html).toContain('openAdminDialog');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 327
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  325 |     const resp = await ctx.get(BASE_URL);\n  326 |     const html = await resp.text();\n> 327 |     expect(html).toContain('openDeactivateDialog');\n      |                  ^\n  328 |     expect(html).toContain('openReactivateDialog');\n  329 |     expect(html).toContain('openRenameDialog');\n  330 |     expect(html).toContain('openAdminDialog');\n    at /var/www/360lm/tests/custodian.spec.js:327:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:10:37.923Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-8f2b2-vate-rename-admin-functions-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 327
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-507c8ed4751aa5582162",
              "file": "custodian.spec.js",
              "line": 323,
              "column": 3
            },
            {
              "title": "manage screen has add-admin-user form",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 150,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:340:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 340
                        },
                        "snippet": "  338 |     const resp = await ctx.get(BASE_URL);\n  339 |     const html = await resp.text();\n> 340 |     expect(html).toContain('id=\"au-id\"');\n      |                  ^\n  341 |     expect(html).toContain('id=\"au-name\"');\n  342 |     expect(html).toContain('id=\"au-title\"');\n  343 |     expect(html).toContain('addAdminUser');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 340
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  338 |     const resp = await ctx.get(BASE_URL);\n  339 |     const html = await resp.text();\n> 340 |     expect(html).toContain('id=\"au-id\"');\n      |                  ^\n  341 |     expect(html).toContain('id=\"au-name\"');\n  342 |     expect(html).toContain('id=\"au-title\"');\n  343 |     expect(html).toContain('addAdminUser');\n    at /var/www/360lm/tests/custodian.spec.js:340:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:10:38.856Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f5efb-een-has-add-admin-user-form-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 340
                      }
                    },
                    {
                      "workerIndex": 151,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:340:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 340
                        },
                        "snippet": "  338 |     const resp = await ctx.get(BASE_URL);\n  339 |     const html = await resp.text();\n> 340 |     expect(html).toContain('id=\"au-id\"');\n      |                  ^\n  341 |     expect(html).toContain('id=\"au-name\"');\n  342 |     expect(html).toContain('id=\"au-title\"');\n  343 |     expect(html).toContain('addAdminUser');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 340
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  338 |     const resp = await ctx.get(BASE_URL);\n  339 |     const html = await resp.text();\n> 340 |     expect(html).toContain('id=\"au-id\"');\n      |                  ^\n  341 |     expect(html).toContain('id=\"au-name\"');\n  342 |     expect(html).toContain('id=\"au-title\"');\n  343 |     expect(html).toContain('addAdminUser');\n    at /var/www/360lm/tests/custodian.spec.js:340:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:10:39.758Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f5efb-een-has-add-admin-user-form-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 340
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-162444347085b7c2937b",
              "file": "custodian.spec.js",
              "line": 336,
              "column": 3
            },
            {
              "title": "cust-mgmt-modal and dialogs present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 152,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:351:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 351
                        },
                        "snippet": "  349 |     const resp = await ctx.get(BASE_URL);\n  350 |     const html = await resp.text();\n> 351 |     expect(html).toContain('id=\"cust-mgmt-modal\"');\n      |                  ^\n  352 |     expect(html).toContain('closeCustMgmt');\n  353 |     expect(html).toContain('doDeactivate');\n  354 |     expect(html).toContain('doReactivate');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 351
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  349 |     const resp = await ctx.get(BASE_URL);\n  350 |     const html = await resp.text();\n> 351 |     expect(html).toContain('id=\"cust-mgmt-modal\"');\n      |                  ^\n  352 |     expect(html).toContain('closeCustMgmt');\n  353 |     expect(html).toContain('doDeactivate');\n  354 |     expect(html).toContain('doReactivate');\n    at /var/www/360lm/tests/custodian.spec.js:351:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:10:40.695Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-45afe-t-modal-and-dialogs-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 351
                      }
                    },
                    {
                      "workerIndex": 153,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:351:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 351
                        },
                        "snippet": "  349 |     const resp = await ctx.get(BASE_URL);\n  350 |     const html = await resp.text();\n> 351 |     expect(html).toContain('id=\"cust-mgmt-modal\"');\n      |                  ^\n  352 |     expect(html).toContain('closeCustMgmt');\n  353 |     expect(html).toContain('doDeactivate');\n  354 |     expect(html).toContain('doReactivate');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 351
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  349 |     const resp = await ctx.get(BASE_URL);\n  350 |     const html = await resp.text();\n> 351 |     expect(html).toContain('id=\"cust-mgmt-modal\"');\n      |                  ^\n  352 |     expect(html).toContain('closeCustMgmt');\n  353 |     expect(html).toContain('doDeactivate');\n  354 |     expect(html).toContain('doReactivate');\n    at /var/www/360lm/tests/custodian.spec.js:351:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:10:41.642Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-45afe-t-modal-and-dialogs-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 351
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-512d6a9a9b53c274eb1a",
              "file": "custodian.spec.js",
              "line": 347,
              "column": 3
            },
            {
              "title": "display_title used for OPS overview (no hardcoded pramod check)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 154,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:363:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 363
                        },
                        "snippet": "  361 |     const html = await resp.text();\n  362 |     expect(html).not.toContain(\"myId === 'pramod'\");\n> 363 |     expect(html).toContain('display_title');\n      |                  ^\n  364 |     await ctx.dispose();\n  365 |   });\n  366 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 363
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  361 |     const html = await resp.text();\n  362 |     expect(html).not.toContain(\"myId === 'pramod'\");\n> 363 |     expect(html).toContain('display_title');\n      |                  ^\n  364 |     await ctx.dispose();\n  365 |   });\n  366 |\n    at /var/www/360lm/tests/custodian.spec.js:363:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:10:42.587Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-86bcb--no-hardcoded-pramod-check--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 363
                      }
                    },
                    {
                      "workerIndex": 155,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:363:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 363
                        },
                        "snippet": "  361 |     const html = await resp.text();\n  362 |     expect(html).not.toContain(\"myId === 'pramod'\");\n> 363 |     expect(html).toContain('display_title');\n      |                  ^\n  364 |     await ctx.dispose();\n  365 |   });\n  366 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 363
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  361 |     const html = await resp.text();\n  362 |     expect(html).not.toContain(\"myId === 'pramod'\");\n> 363 |     expect(html).toContain('display_title');\n      |                  ^\n  364 |     await ctx.dispose();\n  365 |   });\n  366 |\n    at /var/www/360lm/tests/custodian.spec.js:363:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:10:43.524Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-86bcb--no-hardcoded-pramod-check--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 363
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-4c259139fd764469c1e3",
              "file": "custodian.spec.js",
              "line": 358,
              "column": 3
            },
            {
              "title": "limited-mode banner and submitTransfer guard present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 156,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 50,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:371:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 371
                        },
                        "snippet": "  369 |     const resp = await ctx.get(BASE_URL);\n  370 |     const html = await resp.text();\n> 371 |     expect(html).toContain('Settlement Access Only');\n      |                  ^\n  372 |     expect(html).toContain('_isLimitedMode');\n  373 |     await ctx.dispose();\n  374 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 371
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  369 |     const resp = await ctx.get(BASE_URL);\n  370 |     const html = await resp.text();\n> 371 |     expect(html).toContain('Settlement Access Only');\n      |                  ^\n  372 |     expect(html).toContain('_isLimitedMode');\n  373 |     await ctx.dispose();\n  374 |   });\n    at /var/www/360lm/tests/custodian.spec.js:371:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:10:44.401Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-627c3-ubmitTransfer-guard-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 371
                      }
                    },
                    {
                      "workerIndex": 157,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:371:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 371
                        },
                        "snippet": "  369 |     const resp = await ctx.get(BASE_URL);\n  370 |     const html = await resp.text();\n> 371 |     expect(html).toContain('Settlement Access Only');\n      |                  ^\n  372 |     expect(html).toContain('_isLimitedMode');\n  373 |     await ctx.dispose();\n  374 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 371
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  369 |     const resp = await ctx.get(BASE_URL);\n  370 |     const html = await resp.text();\n> 371 |     expect(html).toContain('Settlement Access Only');\n      |                  ^\n  372 |     expect(html).toContain('_isLimitedMode');\n  373 |     await ctx.dispose();\n  374 |   });\n    at /var/www/360lm/tests/custodian.spec.js:371:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:10:45.323Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-627c3-ubmitTransfer-guard-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 371
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-a347fcd2b7ebbd6ecb35",
              "file": "custodian.spec.js",
              "line": 367,
              "column": 3
            },
            {
              "title": "hardcoded ADMIN_IDS and CUSTODIAN_IDS arrays are removed",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1151,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 59,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:09.824Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e1e49def2648bf8aa609-65c972edb475903c4c6b",
              "file": "custodian.spec.js",
              "line": 295,
              "column": 3
            },
            {
              "title": "DB-driven auth — _myProfile and _isLimitedMode state vars present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1151,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:308:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 308
                        },
                        "snippet": "  306 |     const resp = await ctx.get(BASE_URL);\n  307 |     const html = await resp.text();\n> 308 |     expect(html).toContain('_myProfile');\n      |                  ^\n  309 |     expect(html).toContain('_isLimitedMode');\n  310 |     expect(html).toContain('profiles?custodian_id=eq.');\n  311 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 308
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  306 |     const resp = await ctx.get(BASE_URL);\n  307 |     const html = await resp.text();\n> 308 |     expect(html).toContain('_myProfile');\n      |                  ^\n  309 |     expect(html).toContain('_isLimitedMode');\n  310 |     expect(html).toContain('profiles?custodian_id=eq.');\n  311 |     await ctx.dispose();\n    at /var/www/360lm/tests/custodian.spec.js:308:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:09.934Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6d889-itedMode-state-vars-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 308
                      }
                    },
                    {
                      "workerIndex": 1152,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 65,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:308:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 308
                        },
                        "snippet": "  306 |     const resp = await ctx.get(BASE_URL);\n  307 |     const html = await resp.text();\n> 308 |     expect(html).toContain('_myProfile');\n      |                  ^\n  309 |     expect(html).toContain('_isLimitedMode');\n  310 |     expect(html).toContain('profiles?custodian_id=eq.');\n  311 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 308
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_myProfile\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  306 |     const resp = await ctx.get(BASE_URL);\n  307 |     const html = await resp.text();\n> 308 |     expect(html).toContain('_myProfile');\n      |                  ^\n  309 |     expect(html).toContain('_isLimitedMode');\n  310 |     expect(html).toContain('profiles?custodian_id=eq.');\n  311 |     await ctx.dispose();\n    at /var/www/360lm/tests/custodian.spec.js:308:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:10:10.956Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-6d889-itedMode-state-vars-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 308
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-5ee7142f3c82df94ed63",
              "file": "custodian.spec.js",
              "line": 304,
              "column": 3
            },
            {
              "title": "deactivated-custodian \"Access Removed\" screen markup present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1153,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:318:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 318
                        },
                        "snippet": "  316 |     const resp = await ctx.get(BASE_URL);\n  317 |     const html = await resp.text();\n> 318 |     expect(html).toContain('Access Removed');\n      |                  ^\n  319 |     expect(html).toContain('Your custodian access has been deactivated');\n  320 |     await ctx.dispose();\n  321 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 318
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  316 |     const resp = await ctx.get(BASE_URL);\n  317 |     const html = await resp.text();\n> 318 |     expect(html).toContain('Access Removed');\n      |                  ^\n  319 |     expect(html).toContain('Your custodian access has been deactivated');\n  320 |     await ctx.dispose();\n  321 |   });\n    at /var/www/360lm/tests/custodian.spec.js:318:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:11.915Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-e46ed-moved-screen-markup-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 318
                      }
                    },
                    {
                      "workerIndex": 1154,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:318:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 318
                        },
                        "snippet": "  316 |     const resp = await ctx.get(BASE_URL);\n  317 |     const html = await resp.text();\n> 318 |     expect(html).toContain('Access Removed');\n      |                  ^\n  319 |     expect(html).toContain('Your custodian access has been deactivated');\n  320 |     await ctx.dispose();\n  321 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 318
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Access Removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  316 |     const resp = await ctx.get(BASE_URL);\n  317 |     const html = await resp.text();\n> 318 |     expect(html).toContain('Access Removed');\n      |                  ^\n  319 |     expect(html).toContain('Your custodian access has been deactivated');\n  320 |     await ctx.dispose();\n  321 |   });\n    at /var/www/360lm/tests/custodian.spec.js:318:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:10:12.839Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-e46ed-moved-screen-markup-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 318
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-da32bb35e65c8d136db8",
              "file": "custodian.spec.js",
              "line": 314,
              "column": 3
            },
            {
              "title": "manage screen has deactivate / reactivate / rename / admin functions",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1155,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:327:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 327
                        },
                        "snippet": "  325 |     const resp = await ctx.get(BASE_URL);\n  326 |     const html = await resp.text();\n> 327 |     expect(html).toContain('openDeactivateDialog');\n      |                  ^\n  328 |     expect(html).toContain('openReactivateDialog');\n  329 |     expect(html).toContain('openRenameDialog');\n  330 |     expect(html).toContain('openAdminDialog');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 327
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  325 |     const resp = await ctx.get(BASE_URL);\n  326 |     const html = await resp.text();\n> 327 |     expect(html).toContain('openDeactivateDialog');\n      |                  ^\n  328 |     expect(html).toContain('openReactivateDialog');\n  329 |     expect(html).toContain('openRenameDialog');\n  330 |     expect(html).toContain('openAdminDialog');\n    at /var/www/360lm/tests/custodian.spec.js:327:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:13.748Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-8f2b2-vate-rename-admin-functions-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 327
                      }
                    },
                    {
                      "workerIndex": 1156,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:327:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 327
                        },
                        "snippet": "  325 |     const resp = await ctx.get(BASE_URL);\n  326 |     const html = await resp.text();\n> 327 |     expect(html).toContain('openDeactivateDialog');\n      |                  ^\n  328 |     expect(html).toContain('openReactivateDialog');\n  329 |     expect(html).toContain('openRenameDialog');\n  330 |     expect(html).toContain('openAdminDialog');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 327
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openDeactivateDialog\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  325 |     const resp = await ctx.get(BASE_URL);\n  326 |     const html = await resp.text();\n> 327 |     expect(html).toContain('openDeactivateDialog');\n      |                  ^\n  328 |     expect(html).toContain('openReactivateDialog');\n  329 |     expect(html).toContain('openRenameDialog');\n  330 |     expect(html).toContain('openAdminDialog');\n    at /var/www/360lm/tests/custodian.spec.js:327:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:10:14.691Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-8f2b2-vate-rename-admin-functions-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 327
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-d851cf0de60ebbe05d8a",
              "file": "custodian.spec.js",
              "line": 323,
              "column": 3
            },
            {
              "title": "manage screen has add-admin-user form",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1157,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:340:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 340
                        },
                        "snippet": "  338 |     const resp = await ctx.get(BASE_URL);\n  339 |     const html = await resp.text();\n> 340 |     expect(html).toContain('id=\"au-id\"');\n      |                  ^\n  341 |     expect(html).toContain('id=\"au-name\"');\n  342 |     expect(html).toContain('id=\"au-title\"');\n  343 |     expect(html).toContain('addAdminUser');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 340
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  338 |     const resp = await ctx.get(BASE_URL);\n  339 |     const html = await resp.text();\n> 340 |     expect(html).toContain('id=\"au-id\"');\n      |                  ^\n  341 |     expect(html).toContain('id=\"au-name\"');\n  342 |     expect(html).toContain('id=\"au-title\"');\n  343 |     expect(html).toContain('addAdminUser');\n    at /var/www/360lm/tests/custodian.spec.js:340:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:15.605Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f5efb-een-has-add-admin-user-form-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 340
                      }
                    },
                    {
                      "workerIndex": 1158,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:340:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 340
                        },
                        "snippet": "  338 |     const resp = await ctx.get(BASE_URL);\n  339 |     const html = await resp.text();\n> 340 |     expect(html).toContain('id=\"au-id\"');\n      |                  ^\n  341 |     expect(html).toContain('id=\"au-name\"');\n  342 |     expect(html).toContain('id=\"au-title\"');\n  343 |     expect(html).toContain('addAdminUser');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 340
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"au-id\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  338 |     const resp = await ctx.get(BASE_URL);\n  339 |     const html = await resp.text();\n> 340 |     expect(html).toContain('id=\"au-id\"');\n      |                  ^\n  341 |     expect(html).toContain('id=\"au-name\"');\n  342 |     expect(html).toContain('id=\"au-title\"');\n  343 |     expect(html).toContain('addAdminUser');\n    at /var/www/360lm/tests/custodian.spec.js:340:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:10:16.537Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f5efb-een-has-add-admin-user-form-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 340
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-e364c0e469ee02e3d2e9",
              "file": "custodian.spec.js",
              "line": 336,
              "column": 3
            },
            {
              "title": "cust-mgmt-modal and dialogs present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1159,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:351:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 351
                        },
                        "snippet": "  349 |     const resp = await ctx.get(BASE_URL);\n  350 |     const html = await resp.text();\n> 351 |     expect(html).toContain('id=\"cust-mgmt-modal\"');\n      |                  ^\n  352 |     expect(html).toContain('closeCustMgmt');\n  353 |     expect(html).toContain('doDeactivate');\n  354 |     expect(html).toContain('doReactivate');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 351
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  349 |     const resp = await ctx.get(BASE_URL);\n  350 |     const html = await resp.text();\n> 351 |     expect(html).toContain('id=\"cust-mgmt-modal\"');\n      |                  ^\n  352 |     expect(html).toContain('closeCustMgmt');\n  353 |     expect(html).toContain('doDeactivate');\n  354 |     expect(html).toContain('doReactivate');\n    at /var/www/360lm/tests/custodian.spec.js:351:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:17.487Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-45afe-t-modal-and-dialogs-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 351
                      }
                    },
                    {
                      "workerIndex": 1160,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:351:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 351
                        },
                        "snippet": "  349 |     const resp = await ctx.get(BASE_URL);\n  350 |     const html = await resp.text();\n> 351 |     expect(html).toContain('id=\"cust-mgmt-modal\"');\n      |                  ^\n  352 |     expect(html).toContain('closeCustMgmt');\n  353 |     expect(html).toContain('doDeactivate');\n  354 |     expect(html).toContain('doReactivate');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 351
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"cust-mgmt-modal\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  349 |     const resp = await ctx.get(BASE_URL);\n  350 |     const html = await resp.text();\n> 351 |     expect(html).toContain('id=\"cust-mgmt-modal\"');\n      |                  ^\n  352 |     expect(html).toContain('closeCustMgmt');\n  353 |     expect(html).toContain('doDeactivate');\n  354 |     expect(html).toContain('doReactivate');\n    at /var/www/360lm/tests/custodian.spec.js:351:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:10:18.372Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-45afe-t-modal-and-dialogs-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 351
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-2f39c26e16e5dbbb0bff",
              "file": "custodian.spec.js",
              "line": 347,
              "column": 3
            },
            {
              "title": "display_title used for OPS overview (no hardcoded pramod check)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1161,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:363:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 363
                        },
                        "snippet": "  361 |     const html = await resp.text();\n  362 |     expect(html).not.toContain(\"myId === 'pramod'\");\n> 363 |     expect(html).toContain('display_title');\n      |                  ^\n  364 |     await ctx.dispose();\n  365 |   });\n  366 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 363
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  361 |     const html = await resp.text();\n  362 |     expect(html).not.toContain(\"myId === 'pramod'\");\n> 363 |     expect(html).toContain('display_title');\n      |                  ^\n  364 |     await ctx.dispose();\n  365 |   });\n  366 |\n    at /var/www/360lm/tests/custodian.spec.js:363:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:19.330Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-86bcb--no-hardcoded-pramod-check--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 363
                      }
                    },
                    {
                      "workerIndex": 1162,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:363:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 363
                        },
                        "snippet": "  361 |     const html = await resp.text();\n  362 |     expect(html).not.toContain(\"myId === 'pramod'\");\n> 363 |     expect(html).toContain('display_title');\n      |                  ^\n  364 |     await ctx.dispose();\n  365 |   });\n  366 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 363
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"display_title\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  361 |     const html = await resp.text();\n  362 |     expect(html).not.toContain(\"myId === 'pramod'\");\n> 363 |     expect(html).toContain('display_title');\n      |                  ^\n  364 |     await ctx.dispose();\n  365 |   });\n  366 |\n    at /var/www/360lm/tests/custodian.spec.js:363:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:10:20.316Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-86bcb--no-hardcoded-pramod-check--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 363
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-bf18198e1e1f4d39b360",
              "file": "custodian.spec.js",
              "line": 358,
              "column": 3
            },
            {
              "title": "limited-mode banner and submitTransfer guard present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1163,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:371:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 371
                        },
                        "snippet": "  369 |     const resp = await ctx.get(BASE_URL);\n  370 |     const html = await resp.text();\n> 371 |     expect(html).toContain('Settlement Access Only');\n      |                  ^\n  372 |     expect(html).toContain('_isLimitedMode');\n  373 |     await ctx.dispose();\n  374 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 371
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  369 |     const resp = await ctx.get(BASE_URL);\n  370 |     const html = await resp.text();\n> 371 |     expect(html).toContain('Settlement Access Only');\n      |                  ^\n  372 |     expect(html).toContain('_isLimitedMode');\n  373 |     await ctx.dispose();\n  374 |   });\n    at /var/www/360lm/tests/custodian.spec.js:371:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:21.249Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-627c3-ubmitTransfer-guard-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 371
                      }
                    },
                    {
                      "workerIndex": 1164,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/custodian.spec.js:371:18",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 18,
                          "line": 371
                        },
                        "snippet": "  369 |     const resp = await ctx.get(BASE_URL);\n  370 |     const html = await resp.text();\n> 371 |     expect(html).toContain('Settlement Access Only');\n      |                  ^\n  372 |     expect(html).toContain('_isLimitedMode');\n  373 |     await ctx.dispose();\n  374 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 18,
                            "line": 371
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Settlement Access Only\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  369 |     const resp = await ctx.get(BASE_URL);\n  370 |     const html = await resp.text();\n> 371 |     expect(html).toContain('Settlement Access Only');\n      |                  ^\n  372 |     expect(html).toContain('_isLimitedMode');\n  373 |     await ctx.dispose();\n  374 |   });\n    at /var/www/360lm/tests/custodian.spec.js:371:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:10:22.262Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-627c3-ubmitTransfer-guard-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 18,
                        "line": 371
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-c365cf35eac5447c398c",
              "file": "custodian.spec.js",
              "line": 367,
              "column": 3
            }
          ]
        },
        {
          "title": "Fund Custodian — v12 Dynamic Custodians (browser)",
          "file": "custodian.spec.js",
          "line": 377,
          "column": 6,
          "specs": [
            {
              "title": "active custodian (shambhu) loads dashboard — DB-driven auth",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 158,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10248,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:381:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 381
                        },
                        "snippet": "  379 |     await injectSession(page, 'shambhu', 'Shambhu');\n  380 |     await page.goto(BASE_URL);\n> 381 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 10000 });\n      |                                           ^\n  382 |   });\n  383 |\n  384 |   test('unknown user (not in profiles) gets Access Restricted', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 381
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  379 |     await injectSession(page, 'shambhu', 'Shambhu');\n  380 |     await page.goto(BASE_URL);\n> 381 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 10000 });\n      |                                           ^\n  382 |   });\n  383 |\n  384 |   test('unknown user (not in profiles) gets Access Restricted', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:381:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:10:46.218Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f3527--dashboard-—-DB-driven-auth-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f3527--dashboard-—-DB-driven-auth-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 381
                      }
                    },
                    {
                      "workerIndex": 159,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10281,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:381:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 381
                        },
                        "snippet": "  379 |     await injectSession(page, 'shambhu', 'Shambhu');\n  380 |     await page.goto(BASE_URL);\n> 381 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 10000 });\n      |                                           ^\n  382 |   });\n  383 |\n  384 |   test('unknown user (not in profiles) gets Access Restricted', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 381
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  379 |     await injectSession(page, 'shambhu', 'Shambhu');\n  380 |     await page.goto(BASE_URL);\n> 381 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 10000 });\n      |                                           ^\n  382 |   });\n  383 |\n  384 |   test('unknown user (not in profiles) gets Access Restricted', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:381:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:10:57.529Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f3527--dashboard-—-DB-driven-auth-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f3527--dashboard-—-DB-driven-auth-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 381
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-8d3405bcccf00dd8791f",
              "file": "custodian.spec.js",
              "line": 378,
              "column": 3
            },
            {
              "title": "unknown user (not in profiles) gets Access Restricted",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 160,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8228,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:387:58",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 58,
                          "line": 387
                        },
                        "snippet": "  385 |     await injectSession(page, 'nobody_xyz', 'Nobody');\n  386 |     await page.goto(BASE_URL);\n> 387 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n      |                                                          ^\n  388 |   });\n  389 |\n  390 |   test('admin (harish) sees manage button — DB-driven is_admin', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 58,
                            "line": 387
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  385 |     await injectSession(page, 'nobody_xyz', 'Nobody');\n  386 |     await page.goto(BASE_URL);\n> 387 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n      |                                                          ^\n  388 |   });\n  389 |\n  390 |   test('admin (harish) sees manage button — DB-driven is_admin', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:387:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:11:08.850Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-023bc-iles-gets-Access-Restricted-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-023bc-iles-gets-Access-Restricted-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 58,
                        "line": 387
                      }
                    },
                    {
                      "workerIndex": 161,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8247,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:387:58",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 58,
                          "line": 387
                        },
                        "snippet": "  385 |     await injectSession(page, 'nobody_xyz', 'Nobody');\n  386 |     await page.goto(BASE_URL);\n> 387 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n      |                                                          ^\n  388 |   });\n  389 |\n  390 |   test('admin (harish) sees manage button — DB-driven is_admin', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 58,
                            "line": 387
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  385 |     await injectSession(page, 'nobody_xyz', 'Nobody');\n  386 |     await page.goto(BASE_URL);\n> 387 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n      |                                                          ^\n  388 |   });\n  389 |\n  390 |   test('admin (harish) sees manage button — DB-driven is_admin', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:387:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:11:18.165Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-023bc-iles-gets-Access-Restricted-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-023bc-iles-gets-Access-Restricted-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 58,
                        "line": 387
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-5815a8b167e70eeacc5d",
              "file": "custodian.spec.js",
              "line": 384,
              "column": 3
            },
            {
              "title": "admin (harish) sees manage button — DB-driven is_admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 162,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8281,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:393:51",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 51,
                          "line": 393
                        },
                        "snippet": "  391 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  392 |     await page.goto(BASE_URL);\n> 393 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  394 |   });\n  395 |\n  396 |   test('admin manage screen shows custodian action buttons', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 51,
                            "line": 393
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  391 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  392 |     await page.goto(BASE_URL);\n> 393 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  394 |   });\n  395 |\n  396 |   test('admin manage screen shows custodian action buttons', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:393:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:11:27.498Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-254ad-button-—-DB-driven-is-admin-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-254ad-button-—-DB-driven-is-admin-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 51,
                        "line": 393
                      }
                    },
                    {
                      "workerIndex": 163,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8264,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:393:51",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 51,
                          "line": 393
                        },
                        "snippet": "  391 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  392 |     await page.goto(BASE_URL);\n> 393 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  394 |   });\n  395 |\n  396 |   test('admin manage screen shows custodian action buttons', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 51,
                            "line": 393
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  391 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  392 |     await page.goto(BASE_URL);\n> 393 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  394 |   });\n  395 |\n  396 |   test('admin manage screen shows custodian action buttons', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:393:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:11:36.861Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-254ad-button-—-DB-driven-is-admin-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-254ad-button-—-DB-driven-is-admin-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 51,
                        "line": 393
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-5ccc31216749527cd7e9",
              "file": "custodian.spec.js",
              "line": 390,
              "column": 3
            },
            {
              "title": "admin manage screen shows custodian action buttons",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 164,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30070,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 400
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  398 |     await page.goto(BASE_URL);\n  399 |     // Navigate to manage screen\n> 400 |     await page.locator('#manage-hdr-btn').click();\n      |                                           ^\n  401 |     // Wait for manage list to load\n  402 |     await page.waitForSelector('.cust-act-btn', { timeout: 8000 });\n  403 |     // Rename button should be visible\n    at /var/www/360lm/tests/custodian.spec.js:400:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:11:46.203Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3416f-ws-custodian-action-buttons-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3416f-ws-custodian-action-buttons-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 165,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30085,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 400
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  398 |     await page.goto(BASE_URL);\n  399 |     // Navigate to manage screen\n> 400 |     await page.locator('#manage-hdr-btn').click();\n      |                                           ^\n  401 |     // Wait for manage list to load\n  402 |     await page.waitForSelector('.cust-act-btn', { timeout: 8000 });\n  403 |     // Rename button should be visible\n    at /var/www/360lm/tests/custodian.spec.js:400:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:12:17.216Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3416f-ws-custodian-action-buttons-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3416f-ws-custodian-action-buttons-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-de3acc686a5974532fc1",
              "file": "custodian.spec.js",
              "line": 396,
              "column": 3
            },
            {
              "title": "admin Harish OPS pill shows DB display_title \"Ops Head\" (not hardcoded)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 166,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8254,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:411:86",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 86,
                          "line": 411
                        },
                        "snippet": "  409 |     await page.goto(BASE_URL);\n  410 |     // Mode selector loads — the Ops Head Overview card should be visible\n> 411 |     await expect(page.locator('.mode-card-title', { hasText: 'Ops Head Overview' })).toBeVisible({ timeout: 8000 });\n      |                                                                                      ^\n  412 |     // Click into overseer mode — pill should show Ops Head title from DB\n  413 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n  414 |     await expect(page.locator('#dash-mode-pill', { hasText: 'Ops Head' })).toBeVisible({ timeout: 8000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 86,
                            "line": 411
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n\n\n  409 |     await page.goto(BASE_URL);\n  410 |     // Mode selector loads — the Ops Head Overview card should be visible\n> 411 |     await expect(page.locator('.mode-card-title', { hasText: 'Ops Head Overview' })).toBeVisible({ timeout: 8000 });\n      |                                                                                      ^\n  412 |     // Click into overseer mode — pill should show Ops Head title from DB\n  413 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n  414 |     await expect(page.locator('#dash-mode-pill', { hasText: 'Ops Head' })).toBeVisible({ timeout: 8000 });\n    at /var/www/360lm/tests/custodian.spec.js:411:86"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:12:48.284Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-879d7-tle-Ops-Head-not-hardcoded--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-879d7-tle-Ops-Head-not-hardcoded--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 86,
                        "line": 411
                      }
                    },
                    {
                      "workerIndex": 167,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:411:86",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 86,
                          "line": 411
                        },
                        "snippet": "  409 |     await page.goto(BASE_URL);\n  410 |     // Mode selector loads — the Ops Head Overview card should be visible\n> 411 |     await expect(page.locator('.mode-card-title', { hasText: 'Ops Head Overview' })).toBeVisible({ timeout: 8000 });\n      |                                                                                      ^\n  412 |     // Click into overseer mode — pill should show Ops Head title from DB\n  413 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n  414 |     await expect(page.locator('#dash-mode-pill', { hasText: 'Ops Head' })).toBeVisible({ timeout: 8000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 86,
                            "line": 411
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n\n\n  409 |     await page.goto(BASE_URL);\n  410 |     // Mode selector loads — the Ops Head Overview card should be visible\n> 411 |     await expect(page.locator('.mode-card-title', { hasText: 'Ops Head Overview' })).toBeVisible({ timeout: 8000 });\n      |                                                                                      ^\n  412 |     // Click into overseer mode — pill should show Ops Head title from DB\n  413 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n  414 |     await expect(page.locator('#dash-mode-pill', { hasText: 'Ops Head' })).toBeVisible({ timeout: 8000 });\n    at /var/www/360lm/tests/custodian.spec.js:411:86"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:12:57.714Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-879d7-tle-Ops-Head-not-hardcoded--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-879d7-tle-Ops-Head-not-hardcoded--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 86,
                        "line": 411
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-bb4295557f95a5c42eb9",
              "file": "custodian.spec.js",
              "line": 407,
              "column": 3
            },
            {
              "title": "active custodian (shambhu) loads dashboard — DB-driven auth",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1165,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10256,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:381:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 381
                        },
                        "snippet": "  379 |     await injectSession(page, 'shambhu', 'Shambhu');\n  380 |     await page.goto(BASE_URL);\n> 381 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 10000 });\n      |                                           ^\n  382 |   });\n  383 |\n  384 |   test('unknown user (not in profiles) gets Access Restricted', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 381
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  379 |     await injectSession(page, 'shambhu', 'Shambhu');\n  380 |     await page.goto(BASE_URL);\n> 381 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 10000 });\n      |                                           ^\n  382 |   });\n  383 |\n  384 |   test('unknown user (not in profiles) gets Access Restricted', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:381:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:23.223Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f3527--dashboard-—-DB-driven-auth-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f3527--dashboard-—-DB-driven-auth-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 381
                      }
                    },
                    {
                      "workerIndex": 1166,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10229,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:381:43",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 43,
                          "line": 381
                        },
                        "snippet": "  379 |     await injectSession(page, 'shambhu', 'Shambhu');\n  380 |     await page.goto(BASE_URL);\n> 381 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 10000 });\n      |                                           ^\n  382 |   });\n  383 |\n  384 |   test('unknown user (not in profiles) gets Access Restricted', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 381
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  379 |     await injectSession(page, 'shambhu', 'Shambhu');\n  380 |     await page.goto(BASE_URL);\n> 381 |     await expect(page.locator('#s-dash')).toBeVisible({ timeout: 10000 });\n      |                                           ^\n  382 |   });\n  383 |\n  384 |   test('unknown user (not in profiles) gets Access Restricted', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:381:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:10:34.500Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f3527--dashboard-—-DB-driven-auth-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-f3527--dashboard-—-DB-driven-auth-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 43,
                        "line": 381
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-962c078644d37ab38596",
              "file": "custodian.spec.js",
              "line": 378,
              "column": 3
            },
            {
              "title": "unknown user (not in profiles) gets Access Restricted",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1167,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8243,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:387:58",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 58,
                          "line": 387
                        },
                        "snippet": "  385 |     await injectSession(page, 'nobody_xyz', 'Nobody');\n  386 |     await page.goto(BASE_URL);\n> 387 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n      |                                                          ^\n  388 |   });\n  389 |\n  390 |   test('admin (harish) sees manage button — DB-driven is_admin', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 58,
                            "line": 387
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  385 |     await injectSession(page, 'nobody_xyz', 'Nobody');\n  386 |     await page.goto(BASE_URL);\n> 387 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n      |                                                          ^\n  388 |   });\n  389 |\n  390 |   test('admin (harish) sees manage button — DB-driven is_admin', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:387:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:10:45.683Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-023bc-iles-gets-Access-Restricted-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-023bc-iles-gets-Access-Restricted-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 58,
                        "line": 387
                      }
                    },
                    {
                      "workerIndex": 1168,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8310,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:387:58",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 58,
                          "line": 387
                        },
                        "snippet": "  385 |     await injectSession(page, 'nobody_xyz', 'Nobody');\n  386 |     await page.goto(BASE_URL);\n> 387 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n      |                                                          ^\n  388 |   });\n  389 |\n  390 |   test('admin (harish) sees manage button — DB-driven is_admin', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 58,
                            "line": 387
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  385 |     await injectSession(page, 'nobody_xyz', 'Nobody');\n  386 |     await page.goto(BASE_URL);\n> 387 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n      |                                                          ^\n  388 |   });\n  389 |\n  390 |   test('admin (harish) sees manage button — DB-driven is_admin', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:387:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:10:54.965Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-023bc-iles-gets-Access-Restricted-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-023bc-iles-gets-Access-Restricted-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 58,
                        "line": 387
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-16e1d07c74c80c72e3cc",
              "file": "custodian.spec.js",
              "line": 384,
              "column": 3
            },
            {
              "title": "admin (harish) sees manage button — DB-driven is_admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1169,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8300,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:393:51",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 51,
                          "line": 393
                        },
                        "snippet": "  391 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  392 |     await page.goto(BASE_URL);\n> 393 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  394 |   });\n  395 |\n  396 |   test('admin manage screen shows custodian action buttons', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 51,
                            "line": 393
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  391 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  392 |     await page.goto(BASE_URL);\n> 393 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  394 |   });\n  395 |\n  396 |   test('admin manage screen shows custodian action buttons', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:393:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:11:04.588Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-254ad-button-—-DB-driven-is-admin-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-254ad-button-—-DB-driven-is-admin-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 51,
                        "line": 393
                      }
                    },
                    {
                      "workerIndex": 1170,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8241,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:393:51",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 51,
                          "line": 393
                        },
                        "snippet": "  391 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  392 |     await page.goto(BASE_URL);\n> 393 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  394 |   });\n  395 |\n  396 |   test('admin manage screen shows custodian action buttons', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 51,
                            "line": 393
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  391 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  392 |     await page.goto(BASE_URL);\n> 393 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  394 |   });\n  395 |\n  396 |   test('admin manage screen shows custodian action buttons', async ({ page }) => {\n    at /var/www/360lm/tests/custodian.spec.js:393:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:11:13.933Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-254ad-button-—-DB-driven-is-admin-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-254ad-button-—-DB-driven-is-admin-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 51,
                        "line": 393
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-1b6ef686953c9b6ecdc7",
              "file": "custodian.spec.js",
              "line": 390,
              "column": 3
            },
            {
              "title": "admin manage screen shows custodian action buttons",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1171,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 400
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  398 |     await page.goto(BASE_URL);\n  399 |     // Navigate to manage screen\n> 400 |     await page.locator('#manage-hdr-btn').click();\n      |                                           ^\n  401 |     // Wait for manage list to load\n  402 |     await page.waitForSelector('.cust-act-btn', { timeout: 8000 });\n  403 |     // Rename button should be visible\n    at /var/www/360lm/tests/custodian.spec.js:400:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:11:23.124Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3416f-ws-custodian-action-buttons-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3416f-ws-custodian-action-buttons-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1172,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30088,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 43,
                            "line": 400
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  398 |     await page.goto(BASE_URL);\n  399 |     // Navigate to manage screen\n> 400 |     await page.locator('#manage-hdr-btn').click();\n      |                                           ^\n  401 |     // Wait for manage list to load\n  402 |     await page.waitForSelector('.cust-act-btn', { timeout: 8000 });\n  403 |     // Rename button should be visible\n    at /var/www/360lm/tests/custodian.spec.js:400:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:11:54.155Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3416f-ws-custodian-action-buttons-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-3416f-ws-custodian-action-buttons-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-6ef34c75fe9b89959df8",
              "file": "custodian.spec.js",
              "line": 396,
              "column": 3
            },
            {
              "title": "admin Harish OPS pill shows DB display_title \"Ops Head\" (not hardcoded)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1173,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8247,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:411:86",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 86,
                          "line": 411
                        },
                        "snippet": "  409 |     await page.goto(BASE_URL);\n  410 |     // Mode selector loads — the Ops Head Overview card should be visible\n> 411 |     await expect(page.locator('.mode-card-title', { hasText: 'Ops Head Overview' })).toBeVisible({ timeout: 8000 });\n      |                                                                                      ^\n  412 |     // Click into overseer mode — pill should show Ops Head title from DB\n  413 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n  414 |     await expect(page.locator('#dash-mode-pill', { hasText: 'Ops Head' })).toBeVisible({ timeout: 8000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 86,
                            "line": 411
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n\n\n  409 |     await page.goto(BASE_URL);\n  410 |     // Mode selector loads — the Ops Head Overview card should be visible\n> 411 |     await expect(page.locator('.mode-card-title', { hasText: 'Ops Head Overview' })).toBeVisible({ timeout: 8000 });\n      |                                                                                      ^\n  412 |     // Click into overseer mode — pill should show Ops Head title from DB\n  413 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n  414 |     await expect(page.locator('#dash-mode-pill', { hasText: 'Ops Head' })).toBeVisible({ timeout: 8000 });\n    at /var/www/360lm/tests/custodian.spec.js:411:86"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:12:25.262Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-879d7-tle-Ops-Head-not-hardcoded--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-879d7-tle-Ops-Head-not-hardcoded--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 86,
                        "line": 411
                      }
                    },
                    {
                      "workerIndex": 1174,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8272,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n\n    at /var/www/360lm/tests/custodian.spec.js:411:86",
                        "location": {
                          "file": "/var/www/360lm/tests/custodian.spec.js",
                          "column": 86,
                          "line": 411
                        },
                        "snippet": "  409 |     await page.goto(BASE_URL);\n  410 |     // Mode selector loads — the Ops Head Overview card should be visible\n> 411 |     await expect(page.locator('.mode-card-title', { hasText: 'Ops Head Overview' })).toBeVisible({ timeout: 8000 });\n      |                                                                                      ^\n  412 |     // Click into overseer mode — pill should show Ops Head title from DB\n  413 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n  414 |     await expect(page.locator('#dash-mode-pill', { hasText: 'Ops Head' })).toBeVisible({ timeout: 8000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/custodian.spec.js",
                            "column": 86,
                            "line": 411
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.mode-card-title').filter({ hasText: 'Ops Head Overview' })\u001b[22m\n\n\n  409 |     await page.goto(BASE_URL);\n  410 |     // Mode selector loads — the Ops Head Overview card should be visible\n> 411 |     await expect(page.locator('.mode-card-title', { hasText: 'Ops Head Overview' })).toBeVisible({ timeout: 8000 });\n      |                                                                                      ^\n  412 |     // Click into overseer mode — pill should show Ops Head title from DB\n  413 |     await page.locator('.mode-card', { hasText: 'Overview' }).click();\n  414 |     await expect(page.locator('#dash-mode-pill', { hasText: 'Ops Head' })).toBeVisible({ timeout: 8000 });\n    at /var/www/360lm/tests/custodian.spec.js:411:86"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:12:34.516Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-879d7-tle-Ops-Head-not-hardcoded--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/custodian-Fund-Custodian-—-879d7-tle-Ops-Head-not-hardcoded--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/custodian.spec.js",
                        "column": 86,
                        "line": 411
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e1e49def2648bf8aa609-23b8e2a6f9835dddee80",
              "file": "custodian.spec.js",
              "line": 407,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "customers.spec.js",
      "file": "customers.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Customer Management — Smoke",
          "file": "customers.spec.js",
          "line": 35,
          "column": 6,
          "specs": [
            {
              "title": "page returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 168,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 272,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:38:28",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 28,
                          "line": 38
                        },
                        "snippet": "  36 |   test('page returns 200', async ({ page }) => {\n  37 |     const resp = await page.goto(BASE_URL);\n> 38 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  39 |   });\n  40 |\n  41 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 28,
                            "line": 38
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  36 |   test('page returns 200', async ({ page }) => {\n  37 |     const resp = await page.goto(BASE_URL);\n> 38 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  39 |   });\n  40 |\n  41 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/customers.spec.js:38:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:07.176Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Management-—-Smoke-page-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Management-—-Smoke-page-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 28,
                        "line": 38
                      }
                    },
                    {
                      "workerIndex": 169,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 361,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:38:28",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 28,
                          "line": 38
                        },
                        "snippet": "  36 |   test('page returns 200', async ({ page }) => {\n  37 |     const resp = await page.goto(BASE_URL);\n> 38 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  39 |   });\n  40 |\n  41 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 28,
                            "line": 38
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  36 |   test('page returns 200', async ({ page }) => {\n  37 |     const resp = await page.goto(BASE_URL);\n> 38 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  39 |   });\n  40 |\n  41 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/customers.spec.js:38:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:08.463Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Management-—-Smoke-page-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Management-—-Smoke-page-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 28,
                        "line": 38
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-e9d6297e3101affe9128",
              "file": "customers.spec.js",
              "line": 36,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 170,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 317,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:43:28",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 28,
                          "line": 43
                        },
                        "snippet": "  41 |   test('manifest.json returns 200', async ({ page }) => {\n  42 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 43 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  44 |   });\n  45 |\n  46 |   test('HTML carries current customers SW version', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 28,
                            "line": 43
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  41 |   test('manifest.json returns 200', async ({ page }) => {\n  42 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 43 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  44 |   });\n  45 |\n  46 |   test('HTML carries current customers SW version', async () => {\n    at /var/www/360lm/tests/customers.spec.js:43:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:09.918Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-eb985-e-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-eb985-e-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 28,
                        "line": 43
                      }
                    },
                    {
                      "workerIndex": 171,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 266,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:43:28",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 28,
                          "line": 43
                        },
                        "snippet": "  41 |   test('manifest.json returns 200', async ({ page }) => {\n  42 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 43 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  44 |   });\n  45 |\n  46 |   test('HTML carries current customers SW version', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 28,
                            "line": 43
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  41 |   test('manifest.json returns 200', async ({ page }) => {\n  42 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 43 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  44 |   });\n  45 |\n  46 |   test('HTML carries current customers SW version', async () => {\n    at /var/www/360lm/tests/customers.spec.js:43:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:11.172Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-eb985-e-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-eb985-e-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 28,
                        "line": 43
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-0df5c1370fbf06584ea1",
              "file": "customers.spec.js",
              "line": 41,
              "column": 3
            },
            {
              "title": "HTML carries current customers SW version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 172,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |     const resp = await ctx.get(BASE_URL);\n  49 |     const html = await resp.text();\n> 50 |     expect(html).toMatch(/customers-v\\d+/);\n     |                  ^\n  51 |     expect(html).toContain('Customer Management');\n  52 |     await ctx.dispose();\n  53 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |     const resp = await ctx.get(BASE_URL);\n  49 |     const html = await resp.text();\n> 50 |     expect(html).toMatch(/customers-v\\d+/);\n     |                  ^\n  51 |     expect(html).toContain('Customer Management');\n  52 |     await ctx.dispose();\n  53 |   });\n    at /var/www/360lm/tests/customers.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:12.485Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8e11e-urrent-customers-SW-version-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    },
                    {
                      "workerIndex": 173,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |     const resp = await ctx.get(BASE_URL);\n  49 |     const html = await resp.text();\n> 50 |     expect(html).toMatch(/customers-v\\d+/);\n     |                  ^\n  51 |     expect(html).toContain('Customer Management');\n  52 |     await ctx.dispose();\n  53 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |     const resp = await ctx.get(BASE_URL);\n  49 |     const html = await resp.text();\n> 50 |     expect(html).toMatch(/customers-v\\d+/);\n     |                  ^\n  51 |     expect(html).toContain('Customer Management');\n  52 |     await ctx.dispose();\n  53 |   });\n    at /var/www/360lm/tests/customers.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:13.393Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8e11e-urrent-customers-SW-version-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-6157cb5a036f2a3a80a8",
              "file": "customers.spec.js",
              "line": 46,
              "column": 3
            },
            {
              "title": "page returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1175,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 271,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:38:28",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 28,
                          "line": 38
                        },
                        "snippet": "  36 |   test('page returns 200', async ({ page }) => {\n  37 |     const resp = await page.goto(BASE_URL);\n> 38 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  39 |   });\n  40 |\n  41 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 28,
                            "line": 38
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  36 |   test('page returns 200', async ({ page }) => {\n  37 |     const resp = await page.goto(BASE_URL);\n> 38 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  39 |   });\n  40 |\n  41 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/customers.spec.js:38:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:12:43.837Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Management-—-Smoke-page-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Management-—-Smoke-page-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 28,
                        "line": 38
                      }
                    },
                    {
                      "workerIndex": 1176,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 295,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:38:28",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 28,
                          "line": 38
                        },
                        "snippet": "  36 |   test('page returns 200', async ({ page }) => {\n  37 |     const resp = await page.goto(BASE_URL);\n> 38 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  39 |   });\n  40 |\n  41 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 28,
                            "line": 38
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  36 |   test('page returns 200', async ({ page }) => {\n  37 |     const resp = await page.goto(BASE_URL);\n> 38 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  39 |   });\n  40 |\n  41 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/customers.spec.js:38:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:12:45.128Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Management-—-Smoke-page-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Management-—-Smoke-page-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 28,
                        "line": 38
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-f165b2460984b233e429",
              "file": "customers.spec.js",
              "line": 36,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1177,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 289,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:43:28",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 28,
                          "line": 43
                        },
                        "snippet": "  41 |   test('manifest.json returns 200', async ({ page }) => {\n  42 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 43 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  44 |   });\n  45 |\n  46 |   test('HTML carries current customers SW version', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 28,
                            "line": 43
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  41 |   test('manifest.json returns 200', async ({ page }) => {\n  42 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 43 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  44 |   });\n  45 |\n  46 |   test('HTML carries current customers SW version', async () => {\n    at /var/www/360lm/tests/customers.spec.js:43:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:12:46.399Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-eb985-e-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-eb985-e-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 28,
                        "line": 43
                      }
                    },
                    {
                      "workerIndex": 1178,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 300,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:43:28",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 28,
                          "line": 43
                        },
                        "snippet": "  41 |   test('manifest.json returns 200', async ({ page }) => {\n  42 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 43 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  44 |   });\n  45 |\n  46 |   test('HTML carries current customers SW version', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 28,
                            "line": 43
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  41 |   test('manifest.json returns 200', async ({ page }) => {\n  42 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 43 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  44 |   });\n  45 |\n  46 |   test('HTML carries current customers SW version', async () => {\n    at /var/www/360lm/tests/customers.spec.js:43:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:12:47.653Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-eb985-e-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-eb985-e-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 28,
                        "line": 43
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-371a4afb2e803856f388",
              "file": "customers.spec.js",
              "line": 41,
              "column": 3
            },
            {
              "title": "HTML carries current customers SW version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1179,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |     const resp = await ctx.get(BASE_URL);\n  49 |     const html = await resp.text();\n> 50 |     expect(html).toMatch(/customers-v\\d+/);\n     |                  ^\n  51 |     expect(html).toContain('Customer Management');\n  52 |     await ctx.dispose();\n  53 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |     const resp = await ctx.get(BASE_URL);\n  49 |     const html = await resp.text();\n> 50 |     expect(html).toMatch(/customers-v\\d+/);\n     |                  ^\n  51 |     expect(html).toContain('Customer Management');\n  52 |     await ctx.dispose();\n  53 |   });\n    at /var/www/360lm/tests/customers.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:12:49.046Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8e11e-urrent-customers-SW-version-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    },
                    {
                      "workerIndex": 1180,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |     const resp = await ctx.get(BASE_URL);\n  49 |     const html = await resp.text();\n> 50 |     expect(html).toMatch(/customers-v\\d+/);\n     |                  ^\n  51 |     expect(html).toContain('Customer Management');\n  52 |     await ctx.dispose();\n  53 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/customers-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |     const resp = await ctx.get(BASE_URL);\n  49 |     const html = await resp.text();\n> 50 |     expect(html).toMatch(/customers-v\\d+/);\n     |                  ^\n  51 |     expect(html).toContain('Customer Management');\n  52 |     await ctx.dispose();\n  53 |   });\n    at /var/www/360lm/tests/customers.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:12:50.090Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8e11e-urrent-customers-SW-version-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-73420dd91226c3c7f835",
              "file": "customers.spec.js",
              "line": 46,
              "column": 3
            }
          ]
        },
        {
          "title": "Customer Management — HTML Structure",
          "file": "customers.spec.js",
          "line": 59,
          "column": 6,
          "specs": [
            {
              "title": "three core screens present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 174,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:68:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 68
                        },
                        "snippet": "  66 |\n  67 |   test('three core screens present', () => {\n> 68 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  69 |     expect(html).toContain('id=\"s-detail\"');\n  70 |     expect(html).toContain('id=\"s-form\"');\n  71 |     expect(html).toContain('id=\"s-pipeline\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  66 |\n  67 |   test('three core screens present', () => {\n> 68 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  69 |     expect(html).toContain('id=\"s-detail\"');\n  70 |     expect(html).toContain('id=\"s-form\"');\n  71 |     expect(html).toContain('id=\"s-pipeline\"');\n    at /var/www/360lm/tests/customers.spec.js:68:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:14.281Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8c9d9--three-core-screens-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 68
                      }
                    },
                    {
                      "workerIndex": 175,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 12,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:68:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 68
                        },
                        "snippet": "  66 |\n  67 |   test('three core screens present', () => {\n> 68 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  69 |     expect(html).toContain('id=\"s-detail\"');\n  70 |     expect(html).toContain('id=\"s-form\"');\n  71 |     expect(html).toContain('id=\"s-pipeline\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  66 |\n  67 |   test('three core screens present', () => {\n> 68 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  69 |     expect(html).toContain('id=\"s-detail\"');\n  70 |     expect(html).toContain('id=\"s-form\"');\n  71 |     expect(html).toContain('id=\"s-pipeline\"');\n    at /var/www/360lm/tests/customers.spec.js:68:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:15.211Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8c9d9--three-core-screens-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 68
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-5f8e39fdb938cceeea27",
              "file": "customers.spec.js",
              "line": 67,
              "column": 3
            },
            {
              "title": "summary tiles present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 176,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:75:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 75
                        },
                        "snippet": "  73 |\n  74 |   test('summary tiles present', () => {\n> 75 |     expect(html).toContain('id=\"t-total\"');\n     |                  ^\n  76 |     expect(html).toContain('id=\"t-business\"');\n  77 |     expect(html).toContain('id=\"t-individual\"');\n  78 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 75
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  73 |\n  74 |   test('summary tiles present', () => {\n> 75 |     expect(html).toContain('id=\"t-total\"');\n     |                  ^\n  76 |     expect(html).toContain('id=\"t-business\"');\n  77 |     expect(html).toContain('id=\"t-individual\"');\n  78 |   });\n    at /var/www/360lm/tests/customers.spec.js:75:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:16.208Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-b84ff-cture-summary-tiles-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 75
                      }
                    },
                    {
                      "workerIndex": 177,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 14,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:75:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 75
                        },
                        "snippet": "  73 |\n  74 |   test('summary tiles present', () => {\n> 75 |     expect(html).toContain('id=\"t-total\"');\n     |                  ^\n  76 |     expect(html).toContain('id=\"t-business\"');\n  77 |     expect(html).toContain('id=\"t-individual\"');\n  78 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 75
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  73 |\n  74 |   test('summary tiles present', () => {\n> 75 |     expect(html).toContain('id=\"t-total\"');\n     |                  ^\n  76 |     expect(html).toContain('id=\"t-business\"');\n  77 |     expect(html).toContain('id=\"t-individual\"');\n  78 |   });\n    at /var/www/360lm/tests/customers.spec.js:75:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:17.146Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-b84ff-cture-summary-tiles-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 75
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-522ecc727dc24c2c7563",
              "file": "customers.spec.js",
              "line": 74,
              "column": 3
            },
            {
              "title": "bottom nav has 3 tabs (List, Pipeline, Add)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 178,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:81:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 81
                        },
                        "snippet": "  79 |\n  80 |   test('bottom nav has 3 tabs (List, Pipeline, Add)', () => {\n> 81 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  82 |     expect(html).toContain('id=\"nav-pipeline\"');\n  83 |     expect(html).toContain('id=\"nav-add\"');\n  84 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 81
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  79 |\n  80 |   test('bottom nav has 3 tabs (List, Pipeline, Add)', () => {\n> 81 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  82 |     expect(html).toContain('id=\"nav-pipeline\"');\n  83 |     expect(html).toContain('id=\"nav-add\"');\n  84 |   });\n    at /var/www/360lm/tests/customers.spec.js:81:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:18.071Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-aa480-s-3-tabs-List-Pipeline-Add--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 81
                      }
                    },
                    {
                      "workerIndex": 179,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:81:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 81
                        },
                        "snippet": "  79 |\n  80 |   test('bottom nav has 3 tabs (List, Pipeline, Add)', () => {\n> 81 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  82 |     expect(html).toContain('id=\"nav-pipeline\"');\n  83 |     expect(html).toContain('id=\"nav-add\"');\n  84 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 81
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  79 |\n  80 |   test('bottom nav has 3 tabs (List, Pipeline, Add)', () => {\n> 81 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  82 |     expect(html).toContain('id=\"nav-pipeline\"');\n  83 |     expect(html).toContain('id=\"nav-add\"');\n  84 |   });\n    at /var/www/360lm/tests/customers.spec.js:81:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:19.055Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-aa480-s-3-tabs-List-Pipeline-Add--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 81
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-62ac64c1180bdbb6c4d9",
              "file": "customers.spec.js",
              "line": 80,
              "column": 3
            },
            {
              "title": "7 detail tabs declared",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 180,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 12,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/customers.spec.js:88:34)\n    at /var/www/360lm/tests/customers.spec.js:88:8",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 34,
                          "line": 88
                        },
                        "snippet": "  86 |   test('7 detail tabs declared', () => {\n  87 |     ['info','contacts','addresses','jobs','offers','deals','comms']\n> 88 |       .forEach(t => expect(html).toContain(`data-tab=\"${t}\"`));\n     |                                  ^\n  89 |   });\n  90 |\n  91 |   test('15 customer form fields present', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 34,
                            "line": 88
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  86 |   test('7 detail tabs declared', () => {\n  87 |     ['info','contacts','addresses','jobs','offers','deals','comms']\n> 88 |       .forEach(t => expect(html).toContain(`data-tab=\"${t}\"`));\n     |                                  ^\n  89 |   });\n  90 |\n  91 |   test('15 customer form fields present', () => {\n    at forEach (/var/www/360lm/tests/customers.spec.js:88:34)\n    at /var/www/360lm/tests/customers.spec.js:88:8"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:20.011Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-7578e-ture-7-detail-tabs-declared-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 34,
                        "line": 88
                      }
                    },
                    {
                      "workerIndex": 181,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/customers.spec.js:88:34)\n    at /var/www/360lm/tests/customers.spec.js:88:8",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 34,
                          "line": 88
                        },
                        "snippet": "  86 |   test('7 detail tabs declared', () => {\n  87 |     ['info','contacts','addresses','jobs','offers','deals','comms']\n> 88 |       .forEach(t => expect(html).toContain(`data-tab=\"${t}\"`));\n     |                                  ^\n  89 |   });\n  90 |\n  91 |   test('15 customer form fields present', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 34,
                            "line": 88
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  86 |   test('7 detail tabs declared', () => {\n  87 |     ['info','contacts','addresses','jobs','offers','deals','comms']\n> 88 |       .forEach(t => expect(html).toContain(`data-tab=\"${t}\"`));\n     |                                  ^\n  89 |   });\n  90 |\n  91 |   test('15 customer form fields present', () => {\n    at forEach (/var/www/360lm/tests/customers.spec.js:88:34)\n    at /var/www/360lm/tests/customers.spec.js:88:8"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:20.979Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-7578e-ture-7-detail-tabs-declared-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 34,
                        "line": 88
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-30c06fcd1c909206d225",
              "file": "customers.spec.js",
              "line": 86,
              "column": 3
            },
            {
              "title": "15 customer form fields present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 182,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/customers.spec.js:94:66)\n    at /var/www/360lm/tests/customers.spec.js:94:39",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 66,
                          "line": 94
                        },
                        "snippet": "  92 |     ['f-name','f-proprietor','f-display','f-gstin','f-pan','f-phone','f-email',\n  93 |      'f-website','f-industry','f-group','f-territory','f-source','f-currency',\n> 94 |      'f-terms','f-aliases','f-notes'].forEach(id => expect(html).toContain(`id=\"${id}\"`));\n     |                                                                  ^\n  95 |   });\n  96 |\n  97 |   test('lookup datalists present', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 66,
                            "line": 94
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  92 |     ['f-name','f-proprietor','f-display','f-gstin','f-pan','f-phone','f-email',\n  93 |      'f-website','f-industry','f-group','f-territory','f-source','f-currency',\n> 94 |      'f-terms','f-aliases','f-notes'].forEach(id => expect(html).toContain(`id=\"${id}\"`));\n     |                                                                  ^\n  95 |   });\n  96 |\n  97 |   test('lookup datalists present', () => {\n    at forEach (/var/www/360lm/tests/customers.spec.js:94:66)\n    at /var/www/360lm/tests/customers.spec.js:94:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:22.008Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-71b96-ustomer-form-fields-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 66,
                        "line": 94
                      }
                    },
                    {
                      "workerIndex": 183,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/customers.spec.js:94:66)\n    at /var/www/360lm/tests/customers.spec.js:94:39",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 66,
                          "line": 94
                        },
                        "snippet": "  92 |     ['f-name','f-proprietor','f-display','f-gstin','f-pan','f-phone','f-email',\n  93 |      'f-website','f-industry','f-group','f-territory','f-source','f-currency',\n> 94 |      'f-terms','f-aliases','f-notes'].forEach(id => expect(html).toContain(`id=\"${id}\"`));\n     |                                                                  ^\n  95 |   });\n  96 |\n  97 |   test('lookup datalists present', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 66,
                            "line": 94
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  92 |     ['f-name','f-proprietor','f-display','f-gstin','f-pan','f-phone','f-email',\n  93 |      'f-website','f-industry','f-group','f-territory','f-source','f-currency',\n> 94 |      'f-terms','f-aliases','f-notes'].forEach(id => expect(html).toContain(`id=\"${id}\"`));\n     |                                                                  ^\n  95 |   });\n  96 |\n  97 |   test('lookup datalists present', () => {\n    at forEach (/var/www/360lm/tests/customers.spec.js:94:66)\n    at /var/www/360lm/tests/customers.spec.js:94:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:23.072Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-71b96-ustomer-form-fields-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 66,
                        "line": 94
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-cc9a0fe79859a1118ed0",
              "file": "customers.spec.js",
              "line": 91,
              "column": 3
            },
            {
              "title": "lookup datalists present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 184,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:98:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 98
                        },
                        "snippet": "   96 |\n   97 |   test('lookup datalists present', () => {\n>  98 |     expect(html).toContain('id=\"dl-industries\"');\n      |                  ^\n   99 |     expect(html).toContain('id=\"dl-groups\"');\n  100 |     expect(html).toContain('id=\"dl-territories\"');\n  101 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 98
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n   96 |\n   97 |   test('lookup datalists present', () => {\n>  98 |     expect(html).toContain('id=\"dl-industries\"');\n      |                  ^\n   99 |     expect(html).toContain('id=\"dl-groups\"');\n  100 |     expect(html).toContain('id=\"dl-territories\"');\n  101 |   });\n    at /var/www/360lm/tests/customers.spec.js:98:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:23.992Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-0e8d8-re-lookup-datalists-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 98
                      }
                    },
                    {
                      "workerIndex": 185,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 14,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:98:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 98
                        },
                        "snippet": "   96 |\n   97 |   test('lookup datalists present', () => {\n>  98 |     expect(html).toContain('id=\"dl-industries\"');\n      |                  ^\n   99 |     expect(html).toContain('id=\"dl-groups\"');\n  100 |     expect(html).toContain('id=\"dl-territories\"');\n  101 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 98
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n   96 |\n   97 |   test('lookup datalists present', () => {\n>  98 |     expect(html).toContain('id=\"dl-industries\"');\n      |                  ^\n   99 |     expect(html).toContain('id=\"dl-groups\"');\n  100 |     expect(html).toContain('id=\"dl-territories\"');\n  101 |   });\n    at /var/www/360lm/tests/customers.spec.js:98:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:24.897Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-0e8d8-re-lookup-datalists-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 98
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-b7a0df22874b33a82d1c",
              "file": "customers.spec.js",
              "line": 97,
              "column": 3
            },
            {
              "title": "lead modal + opp modal present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 186,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 12,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:104:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 104
                        },
                        "snippet": "  102 |\n  103 |   test('lead modal + opp modal present', () => {\n> 104 |     expect(html).toContain('id=\"modal-lead\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"modal-opp\"');\n  106 |     expect(html).toContain('id=\"modal-contact\"');\n  107 |     expect(html).toContain('id=\"modal-address\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 104
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  102 |\n  103 |   test('lead modal + opp modal present', () => {\n> 104 |     expect(html).toContain('id=\"modal-lead\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"modal-opp\"');\n  106 |     expect(html).toContain('id=\"modal-contact\"');\n  107 |     expect(html).toContain('id=\"modal-address\"');\n    at /var/www/360lm/tests/customers.spec.js:104:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:25.853Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-531b9-ead-modal-opp-modal-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 104
                      }
                    },
                    {
                      "workerIndex": 187,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:104:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 104
                        },
                        "snippet": "  102 |\n  103 |   test('lead modal + opp modal present', () => {\n> 104 |     expect(html).toContain('id=\"modal-lead\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"modal-opp\"');\n  106 |     expect(html).toContain('id=\"modal-contact\"');\n  107 |     expect(html).toContain('id=\"modal-address\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 104
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  102 |\n  103 |   test('lead modal + opp modal present', () => {\n> 104 |     expect(html).toContain('id=\"modal-lead\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"modal-opp\"');\n  106 |     expect(html).toContain('id=\"modal-contact\"');\n  107 |     expect(html).toContain('id=\"modal-address\"');\n    at /var/www/360lm/tests/customers.spec.js:104:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:26.859Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-531b9-ead-modal-opp-modal-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 104
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-8df24b41a5fbf04a1cce",
              "file": "customers.spec.js",
              "line": 103,
              "column": 3
            },
            {
              "title": "three core screens present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1181,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:68:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 68
                        },
                        "snippet": "  66 |\n  67 |   test('three core screens present', () => {\n> 68 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  69 |     expect(html).toContain('id=\"s-detail\"');\n  70 |     expect(html).toContain('id=\"s-form\"');\n  71 |     expect(html).toContain('id=\"s-pipeline\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  66 |\n  67 |   test('three core screens present', () => {\n> 68 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  69 |     expect(html).toContain('id=\"s-detail\"');\n  70 |     expect(html).toContain('id=\"s-form\"');\n  71 |     expect(html).toContain('id=\"s-pipeline\"');\n    at /var/www/360lm/tests/customers.spec.js:68:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:12:51.087Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8c9d9--three-core-screens-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 68
                      }
                    },
                    {
                      "workerIndex": 1182,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 17,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:68:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 68
                        },
                        "snippet": "  66 |\n  67 |   test('three core screens present', () => {\n> 68 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  69 |     expect(html).toContain('id=\"s-detail\"');\n  70 |     expect(html).toContain('id=\"s-form\"');\n  71 |     expect(html).toContain('id=\"s-pipeline\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  66 |\n  67 |   test('three core screens present', () => {\n> 68 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  69 |     expect(html).toContain('id=\"s-detail\"');\n  70 |     expect(html).toContain('id=\"s-form\"');\n  71 |     expect(html).toContain('id=\"s-pipeline\"');\n    at /var/www/360lm/tests/customers.spec.js:68:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:12:52.056Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8c9d9--three-core-screens-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 68
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-f73b7e5e85b88318561a",
              "file": "customers.spec.js",
              "line": 67,
              "column": 3
            },
            {
              "title": "summary tiles present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1183,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:75:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 75
                        },
                        "snippet": "  73 |\n  74 |   test('summary tiles present', () => {\n> 75 |     expect(html).toContain('id=\"t-total\"');\n     |                  ^\n  76 |     expect(html).toContain('id=\"t-business\"');\n  77 |     expect(html).toContain('id=\"t-individual\"');\n  78 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 75
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  73 |\n  74 |   test('summary tiles present', () => {\n> 75 |     expect(html).toContain('id=\"t-total\"');\n     |                  ^\n  76 |     expect(html).toContain('id=\"t-business\"');\n  77 |     expect(html).toContain('id=\"t-individual\"');\n  78 |   });\n    at /var/www/360lm/tests/customers.spec.js:75:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:12:53.075Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-b84ff-cture-summary-tiles-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 75
                      }
                    },
                    {
                      "workerIndex": 1184,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 14,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:75:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 75
                        },
                        "snippet": "  73 |\n  74 |   test('summary tiles present', () => {\n> 75 |     expect(html).toContain('id=\"t-total\"');\n     |                  ^\n  76 |     expect(html).toContain('id=\"t-business\"');\n  77 |     expect(html).toContain('id=\"t-individual\"');\n  78 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 75
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"t-total\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  73 |\n  74 |   test('summary tiles present', () => {\n> 75 |     expect(html).toContain('id=\"t-total\"');\n     |                  ^\n  76 |     expect(html).toContain('id=\"t-business\"');\n  77 |     expect(html).toContain('id=\"t-individual\"');\n  78 |   });\n    at /var/www/360lm/tests/customers.spec.js:75:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:12:54.073Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-b84ff-cture-summary-tiles-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 75
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-efa840ec357d99650046",
              "file": "customers.spec.js",
              "line": 74,
              "column": 3
            },
            {
              "title": "bottom nav has 3 tabs (List, Pipeline, Add)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1185,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 15,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:81:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 81
                        },
                        "snippet": "  79 |\n  80 |   test('bottom nav has 3 tabs (List, Pipeline, Add)', () => {\n> 81 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  82 |     expect(html).toContain('id=\"nav-pipeline\"');\n  83 |     expect(html).toContain('id=\"nav-add\"');\n  84 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 81
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  79 |\n  80 |   test('bottom nav has 3 tabs (List, Pipeline, Add)', () => {\n> 81 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  82 |     expect(html).toContain('id=\"nav-pipeline\"');\n  83 |     expect(html).toContain('id=\"nav-add\"');\n  84 |   });\n    at /var/www/360lm/tests/customers.spec.js:81:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:12:55.228Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-aa480-s-3-tabs-List-Pipeline-Add--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 81
                      }
                    },
                    {
                      "workerIndex": 1186,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 14,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:81:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 81
                        },
                        "snippet": "  79 |\n  80 |   test('bottom nav has 3 tabs (List, Pipeline, Add)', () => {\n> 81 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  82 |     expect(html).toContain('id=\"nav-pipeline\"');\n  83 |     expect(html).toContain('id=\"nav-add\"');\n  84 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 81
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  79 |\n  80 |   test('bottom nav has 3 tabs (List, Pipeline, Add)', () => {\n> 81 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  82 |     expect(html).toContain('id=\"nav-pipeline\"');\n  83 |     expect(html).toContain('id=\"nav-add\"');\n  84 |   });\n    at /var/www/360lm/tests/customers.spec.js:81:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:12:56.383Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-aa480-s-3-tabs-List-Pipeline-Add--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 81
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-6cabb518a8e5a9312eeb",
              "file": "customers.spec.js",
              "line": 80,
              "column": 3
            },
            {
              "title": "7 detail tabs declared",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1187,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 14,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/customers.spec.js:88:34)\n    at /var/www/360lm/tests/customers.spec.js:88:8",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 34,
                          "line": 88
                        },
                        "snippet": "  86 |   test('7 detail tabs declared', () => {\n  87 |     ['info','contacts','addresses','jobs','offers','deals','comms']\n> 88 |       .forEach(t => expect(html).toContain(`data-tab=\"${t}\"`));\n     |                                  ^\n  89 |   });\n  90 |\n  91 |   test('15 customer form fields present', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 34,
                            "line": 88
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  86 |   test('7 detail tabs declared', () => {\n  87 |     ['info','contacts','addresses','jobs','offers','deals','comms']\n> 88 |       .forEach(t => expect(html).toContain(`data-tab=\"${t}\"`));\n     |                                  ^\n  89 |   });\n  90 |\n  91 |   test('15 customer form fields present', () => {\n    at forEach (/var/www/360lm/tests/customers.spec.js:88:34)\n    at /var/www/360lm/tests/customers.spec.js:88:8"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:12:57.395Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-7578e-ture-7-detail-tabs-declared-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 34,
                        "line": 88
                      }
                    },
                    {
                      "workerIndex": 1188,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/customers.spec.js:88:34)\n    at /var/www/360lm/tests/customers.spec.js:88:8",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 34,
                          "line": 88
                        },
                        "snippet": "  86 |   test('7 detail tabs declared', () => {\n  87 |     ['info','contacts','addresses','jobs','offers','deals','comms']\n> 88 |       .forEach(t => expect(html).toContain(`data-tab=\"${t}\"`));\n     |                                  ^\n  89 |   });\n  90 |\n  91 |   test('15 customer form fields present', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 34,
                            "line": 88
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"info\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  86 |   test('7 detail tabs declared', () => {\n  87 |     ['info','contacts','addresses','jobs','offers','deals','comms']\n> 88 |       .forEach(t => expect(html).toContain(`data-tab=\"${t}\"`));\n     |                                  ^\n  89 |   });\n  90 |\n  91 |   test('15 customer form fields present', () => {\n    at forEach (/var/www/360lm/tests/customers.spec.js:88:34)\n    at /var/www/360lm/tests/customers.spec.js:88:8"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:12:58.431Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-7578e-ture-7-detail-tabs-declared-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 34,
                        "line": 88
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-4f88e1b595a18f55a0a3",
              "file": "customers.spec.js",
              "line": 86,
              "column": 3
            },
            {
              "title": "15 customer form fields present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1189,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 15,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/customers.spec.js:94:66)\n    at /var/www/360lm/tests/customers.spec.js:94:39",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 66,
                          "line": 94
                        },
                        "snippet": "  92 |     ['f-name','f-proprietor','f-display','f-gstin','f-pan','f-phone','f-email',\n  93 |      'f-website','f-industry','f-group','f-territory','f-source','f-currency',\n> 94 |      'f-terms','f-aliases','f-notes'].forEach(id => expect(html).toContain(`id=\"${id}\"`));\n     |                                                                  ^\n  95 |   });\n  96 |\n  97 |   test('lookup datalists present', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 66,
                            "line": 94
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  92 |     ['f-name','f-proprietor','f-display','f-gstin','f-pan','f-phone','f-email',\n  93 |      'f-website','f-industry','f-group','f-territory','f-source','f-currency',\n> 94 |      'f-terms','f-aliases','f-notes'].forEach(id => expect(html).toContain(`id=\"${id}\"`));\n     |                                                                  ^\n  95 |   });\n  96 |\n  97 |   test('lookup datalists present', () => {\n    at forEach (/var/www/360lm/tests/customers.spec.js:94:66)\n    at /var/www/360lm/tests/customers.spec.js:94:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:12:59.472Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-71b96-ustomer-form-fields-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 66,
                        "line": 94
                      }
                    },
                    {
                      "workerIndex": 1190,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 16,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/customers.spec.js:94:66)\n    at /var/www/360lm/tests/customers.spec.js:94:39",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 66,
                          "line": 94
                        },
                        "snippet": "  92 |     ['f-name','f-proprietor','f-display','f-gstin','f-pan','f-phone','f-email',\n  93 |      'f-website','f-industry','f-group','f-territory','f-source','f-currency',\n> 94 |      'f-terms','f-aliases','f-notes'].forEach(id => expect(html).toContain(`id=\"${id}\"`));\n     |                                                                  ^\n  95 |   });\n  96 |\n  97 |   test('lookup datalists present', () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 66,
                            "line": 94
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  92 |     ['f-name','f-proprietor','f-display','f-gstin','f-pan','f-phone','f-email',\n  93 |      'f-website','f-industry','f-group','f-territory','f-source','f-currency',\n> 94 |      'f-terms','f-aliases','f-notes'].forEach(id => expect(html).toContain(`id=\"${id}\"`));\n     |                                                                  ^\n  95 |   });\n  96 |\n  97 |   test('lookup datalists present', () => {\n    at forEach (/var/www/360lm/tests/customers.spec.js:94:66)\n    at /var/www/360lm/tests/customers.spec.js:94:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:13:00.562Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-71b96-ustomer-form-fields-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 66,
                        "line": 94
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-1ddf72a00dc8df77db12",
              "file": "customers.spec.js",
              "line": 91,
              "column": 3
            },
            {
              "title": "lookup datalists present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1191,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 16,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:98:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 98
                        },
                        "snippet": "   96 |\n   97 |   test('lookup datalists present', () => {\n>  98 |     expect(html).toContain('id=\"dl-industries\"');\n      |                  ^\n   99 |     expect(html).toContain('id=\"dl-groups\"');\n  100 |     expect(html).toContain('id=\"dl-territories\"');\n  101 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 98
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n   96 |\n   97 |   test('lookup datalists present', () => {\n>  98 |     expect(html).toContain('id=\"dl-industries\"');\n      |                  ^\n   99 |     expect(html).toContain('id=\"dl-groups\"');\n  100 |     expect(html).toContain('id=\"dl-territories\"');\n  101 |   });\n    at /var/www/360lm/tests/customers.spec.js:98:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:13:01.804Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-0e8d8-re-lookup-datalists-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 98
                      }
                    },
                    {
                      "workerIndex": 1192,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 22,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:98:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 98
                        },
                        "snippet": "   96 |\n   97 |   test('lookup datalists present', () => {\n>  98 |     expect(html).toContain('id=\"dl-industries\"');\n      |                  ^\n   99 |     expect(html).toContain('id=\"dl-groups\"');\n  100 |     expect(html).toContain('id=\"dl-territories\"');\n  101 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 98
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"dl-industries\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n   96 |\n   97 |   test('lookup datalists present', () => {\n>  98 |     expect(html).toContain('id=\"dl-industries\"');\n      |                  ^\n   99 |     expect(html).toContain('id=\"dl-groups\"');\n  100 |     expect(html).toContain('id=\"dl-territories\"');\n  101 |   });\n    at /var/www/360lm/tests/customers.spec.js:98:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:13:03.142Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-0e8d8-re-lookup-datalists-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 98
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-4c0d15d80cce9bbfdfa6",
              "file": "customers.spec.js",
              "line": 97,
              "column": 3
            },
            {
              "title": "lead modal + opp modal present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1193,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 16,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:104:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 104
                        },
                        "snippet": "  102 |\n  103 |   test('lead modal + opp modal present', () => {\n> 104 |     expect(html).toContain('id=\"modal-lead\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"modal-opp\"');\n  106 |     expect(html).toContain('id=\"modal-contact\"');\n  107 |     expect(html).toContain('id=\"modal-address\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 104
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  102 |\n  103 |   test('lead modal + opp modal present', () => {\n> 104 |     expect(html).toContain('id=\"modal-lead\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"modal-opp\"');\n  106 |     expect(html).toContain('id=\"modal-contact\"');\n  107 |     expect(html).toContain('id=\"modal-address\"');\n    at /var/www/360lm/tests/customers.spec.js:104:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:13:04.403Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-531b9-ead-modal-opp-modal-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 104
                      }
                    },
                    {
                      "workerIndex": 1194,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:104:18",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 18,
                          "line": 104
                        },
                        "snippet": "  102 |\n  103 |   test('lead modal + opp modal present', () => {\n> 104 |     expect(html).toContain('id=\"modal-lead\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"modal-opp\"');\n  106 |     expect(html).toContain('id=\"modal-contact\"');\n  107 |     expect(html).toContain('id=\"modal-address\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 18,
                            "line": 104
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-lead\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  102 |\n  103 |   test('lead modal + opp modal present', () => {\n> 104 |     expect(html).toContain('id=\"modal-lead\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"modal-opp\"');\n  106 |     expect(html).toContain('id=\"modal-contact\"');\n  107 |     expect(html).toContain('id=\"modal-address\"');\n    at /var/www/360lm/tests/customers.spec.js:104:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:13:05.481Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-531b9-ead-modal-opp-modal-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 18,
                        "line": 104
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-aae65bac51fc3aec54c3",
              "file": "customers.spec.js",
              "line": 103,
              "column": 3
            }
          ]
        },
        {
          "title": "Customer Management — Seed dataset",
          "file": "customers.spec.js",
          "line": 114,
          "column": 6,
          "specs": [
            {
              "title": "test customers exist in customers_v",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 188,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 102,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:121:25",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 25,
                          "line": 121
                        },
                        "snippet": "  119 |     });\n  120 |     const rows = await r.json();\n> 121 |     expect(rows.length).toBeGreaterThanOrEqual(8);\n      |                         ^\n  122 |     const park = rows.find(c => c.customer_id === 'cm-test-park-hospital');\n  123 |     expect(park).toBeTruthy();\n  124 |     expect(park.contact_count).toBeGreaterThanOrEqual(3);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 25,
                            "line": 121
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  119 |     });\n  120 |     const rows = await r.json();\n> 121 |     expect(rows.length).toBeGreaterThanOrEqual(8);\n      |                         ^\n  122 |     const park = rows.find(c => c.customer_id === 'cm-test-park-hospital');\n  123 |     expect(park).toBeTruthy();\n  124 |     expect(park.contact_count).toBeGreaterThanOrEqual(3);\n    at /var/www/360lm/tests/customers.spec.js:121:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:27.780Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-f511c-tomers-exist-in-customers-v-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 25,
                        "line": 121
                      }
                    },
                    {
                      "workerIndex": 189,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 83,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:121:25",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 25,
                          "line": 121
                        },
                        "snippet": "  119 |     });\n  120 |     const rows = await r.json();\n> 121 |     expect(rows.length).toBeGreaterThanOrEqual(8);\n      |                         ^\n  122 |     const park = rows.find(c => c.customer_id === 'cm-test-park-hospital');\n  123 |     expect(park).toBeTruthy();\n  124 |     expect(park.contact_count).toBeGreaterThanOrEqual(3);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 25,
                            "line": 121
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  119 |     });\n  120 |     const rows = await r.json();\n> 121 |     expect(rows.length).toBeGreaterThanOrEqual(8);\n      |                         ^\n  122 |     const park = rows.find(c => c.customer_id === 'cm-test-park-hospital');\n  123 |     expect(park).toBeTruthy();\n  124 |     expect(park.contact_count).toBeGreaterThanOrEqual(3);\n    at /var/www/360lm/tests/customers.spec.js:121:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:28.696Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-f511c-tomers-exist-in-customers-v-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 25,
                        "line": 121
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-7e54c96a4e3023f9b968",
              "file": "customers.spec.js",
              "line": 115,
              "column": 3
            },
            {
              "title": "test leads cover all 4 active statuses",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 190,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:136:33",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 33,
                          "line": 136
                        },
                        "snippet": "  134 |     const rows = await r.json();\n  135 |     const statuses = new Set(rows.map(r => r.status));\n> 136 |     expect(statuses.has('new')).toBeTruthy();\n      |                                 ^\n  137 |     expect(statuses.has('contacted')).toBeTruthy();\n  138 |     expect(statuses.has('qualified')).toBeTruthy();\n  139 |     expect(statuses.has('disqualified')).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 33,
                            "line": 136
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n\n  134 |     const rows = await r.json();\n  135 |     const statuses = new Set(rows.map(r => r.status));\n> 136 |     expect(statuses.has('new')).toBeTruthy();\n      |                                 ^\n  137 |     expect(statuses.has('contacted')).toBeTruthy();\n  138 |     expect(statuses.has('qualified')).toBeTruthy();\n  139 |     expect(statuses.has('disqualified')).toBeTruthy();\n    at /var/www/360lm/tests/customers.spec.js:136:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:29.603Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-fd9b9-cover-all-4-active-statuses-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 33,
                        "line": 136
                      }
                    },
                    {
                      "workerIndex": 191,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:136:33",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 33,
                          "line": 136
                        },
                        "snippet": "  134 |     const rows = await r.json();\n  135 |     const statuses = new Set(rows.map(r => r.status));\n> 136 |     expect(statuses.has('new')).toBeTruthy();\n      |                                 ^\n  137 |     expect(statuses.has('contacted')).toBeTruthy();\n  138 |     expect(statuses.has('qualified')).toBeTruthy();\n  139 |     expect(statuses.has('disqualified')).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 33,
                            "line": 136
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n\n  134 |     const rows = await r.json();\n  135 |     const statuses = new Set(rows.map(r => r.status));\n> 136 |     expect(statuses.has('new')).toBeTruthy();\n      |                                 ^\n  137 |     expect(statuses.has('contacted')).toBeTruthy();\n  138 |     expect(statuses.has('qualified')).toBeTruthy();\n  139 |     expect(statuses.has('disqualified')).toBeTruthy();\n    at /var/www/360lm/tests/customers.spec.js:136:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:30.474Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-fd9b9-cover-all-4-active-statuses-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 33,
                        "line": 136
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-462eb6b8d4635a7cc5d6",
              "file": "customers.spec.js",
              "line": 129,
              "column": 3
            },
            {
              "title": "opportunities span all 5 stages",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 192,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 89,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:150:41",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 41,
                          "line": 150
                        },
                        "snippet": "  148 |     const rows = await r.json();\n  149 |     const stages = new Set(rows.map(r => r.stage));\n> 150 |     expect(stages.has('qualification')).toBeTruthy();\n      |                                         ^\n  151 |     expect(stages.has('proposal')).toBeTruthy();\n  152 |     expect(stages.has('negotiation')).toBeTruthy();\n  153 |     expect(stages.has('won')).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 41,
                            "line": 150
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n\n  148 |     const rows = await r.json();\n  149 |     const stages = new Set(rows.map(r => r.stage));\n> 150 |     expect(stages.has('qualification')).toBeTruthy();\n      |                                         ^\n  151 |     expect(stages.has('proposal')).toBeTruthy();\n  152 |     expect(stages.has('negotiation')).toBeTruthy();\n  153 |     expect(stages.has('won')).toBeTruthy();\n    at /var/www/360lm/tests/customers.spec.js:150:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:31.413Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-3e407-rtunities-span-all-5-stages-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 41,
                        "line": 150
                      }
                    },
                    {
                      "workerIndex": 193,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 68,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:150:41",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 41,
                          "line": 150
                        },
                        "snippet": "  148 |     const rows = await r.json();\n  149 |     const stages = new Set(rows.map(r => r.stage));\n> 150 |     expect(stages.has('qualification')).toBeTruthy();\n      |                                         ^\n  151 |     expect(stages.has('proposal')).toBeTruthy();\n  152 |     expect(stages.has('negotiation')).toBeTruthy();\n  153 |     expect(stages.has('won')).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 41,
                            "line": 150
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n\n  148 |     const rows = await r.json();\n  149 |     const stages = new Set(rows.map(r => r.stage));\n> 150 |     expect(stages.has('qualification')).toBeTruthy();\n      |                                         ^\n  151 |     expect(stages.has('proposal')).toBeTruthy();\n  152 |     expect(stages.has('negotiation')).toBeTruthy();\n  153 |     expect(stages.has('won')).toBeTruthy();\n    at /var/www/360lm/tests/customers.spec.js:150:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:32.462Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-3e407-rtunities-span-all-5-stages-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 41,
                        "line": 150
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-1bef294406bedfed620e",
              "file": "customers.spec.js",
              "line": 143,
              "column": 3
            },
            {
              "title": "park-hospital 360 reflects offers + opps + comms",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 194,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 76,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:164:15",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 15,
                          "line": 164
                        },
                        "snippet": "  162 |     });\n  163 |     const [s] = await r.json();\n> 164 |     expect(s).toBeTruthy();\n      |               ^\n  165 |     expect(Number(s.opp_count)).toBeGreaterThanOrEqual(2);  // opp-1 + opp-6\n  166 |     expect(Number(s.opp_won)).toBeGreaterThanOrEqual(1);\n  167 |     expect(Number(s.won_value)).toBeGreaterThanOrEqual(520000);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 15,
                            "line": 164
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m\n\n  162 |     });\n  163 |     const [s] = await r.json();\n> 164 |     expect(s).toBeTruthy();\n      |               ^\n  165 |     expect(Number(s.opp_count)).toBeGreaterThanOrEqual(2);  // opp-1 + opp-6\n  166 |     expect(Number(s.opp_won)).toBeGreaterThanOrEqual(1);\n  167 |     expect(Number(s.won_value)).toBeGreaterThanOrEqual(520000);\n    at /var/www/360lm/tests/customers.spec.js:164:15"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:33.394Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-4b61e--reflects-offers-opps-comms-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 15,
                        "line": 164
                      }
                    },
                    {
                      "workerIndex": 195,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:164:15",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 15,
                          "line": 164
                        },
                        "snippet": "  162 |     });\n  163 |     const [s] = await r.json();\n> 164 |     expect(s).toBeTruthy();\n      |               ^\n  165 |     expect(Number(s.opp_count)).toBeGreaterThanOrEqual(2);  // opp-1 + opp-6\n  166 |     expect(Number(s.opp_won)).toBeGreaterThanOrEqual(1);\n  167 |     expect(Number(s.won_value)).toBeGreaterThanOrEqual(520000);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 15,
                            "line": 164
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m\n\n  162 |     });\n  163 |     const [s] = await r.json();\n> 164 |     expect(s).toBeTruthy();\n      |               ^\n  165 |     expect(Number(s.opp_count)).toBeGreaterThanOrEqual(2);  // opp-1 + opp-6\n  166 |     expect(Number(s.opp_won)).toBeGreaterThanOrEqual(1);\n  167 |     expect(Number(s.won_value)).toBeGreaterThanOrEqual(520000);\n    at /var/www/360lm/tests/customers.spec.js:164:15"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:34.321Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-4b61e--reflects-offers-opps-comms-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 15,
                        "line": 164
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-d931f82bdf5f59a36c6c",
              "file": "customers.spec.js",
              "line": 158,
              "column": 3
            },
            {
              "title": "pipeline_by_owner has harish + pramod rows",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 196,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:178:25",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 25,
                          "line": 178
                        },
                        "snippet": "  176 |     });\n  177 |     const rows = await r.json();\n> 178 |     expect(rows.length).toBe(2);\n      |                         ^\n  179 |     rows.forEach(row => {\n  180 |       expect(Number(row.open_count) + Number(row.won_count) + Number(row.lost_count)).toBeGreaterThan(0);\n  181 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 25,
                            "line": 178
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  176 |     });\n  177 |     const rows = await r.json();\n> 178 |     expect(rows.length).toBe(2);\n      |                         ^\n  179 |     rows.forEach(row => {\n  180 |       expect(Number(row.open_count) + Number(row.won_count) + Number(row.lost_count)).toBeGreaterThan(0);\n  181 |     });\n    at /var/www/360lm/tests/customers.spec.js:178:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:35.253Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-0ec8f-wner-has-harish-pramod-rows-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 25,
                        "line": 178
                      }
                    },
                    {
                      "workerIndex": 197,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 80,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:178:25",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 25,
                          "line": 178
                        },
                        "snippet": "  176 |     });\n  177 |     const rows = await r.json();\n> 178 |     expect(rows.length).toBe(2);\n      |                         ^\n  179 |     rows.forEach(row => {\n  180 |       expect(Number(row.open_count) + Number(row.won_count) + Number(row.lost_count)).toBeGreaterThan(0);\n  181 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 25,
                            "line": 178
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  176 |     });\n  177 |     const rows = await r.json();\n> 178 |     expect(rows.length).toBe(2);\n      |                         ^\n  179 |     rows.forEach(row => {\n  180 |       expect(Number(row.open_count) + Number(row.won_count) + Number(row.lost_count)).toBeGreaterThan(0);\n  181 |     });\n    at /var/www/360lm/tests/customers.spec.js:178:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:36.174Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-0ec8f-wner-has-harish-pramod-rows-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 25,
                        "line": 178
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-9535fe7df927f2f0bf8a",
              "file": "customers.spec.js",
              "line": 172,
              "column": 3
            },
            {
              "title": "test customers exist in customers_v",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1195,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 87,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:121:25",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 25,
                          "line": 121
                        },
                        "snippet": "  119 |     });\n  120 |     const rows = await r.json();\n> 121 |     expect(rows.length).toBeGreaterThanOrEqual(8);\n      |                         ^\n  122 |     const park = rows.find(c => c.customer_id === 'cm-test-park-hospital');\n  123 |     expect(park).toBeTruthy();\n  124 |     expect(park.contact_count).toBeGreaterThanOrEqual(3);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 25,
                            "line": 121
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  119 |     });\n  120 |     const rows = await r.json();\n> 121 |     expect(rows.length).toBeGreaterThanOrEqual(8);\n      |                         ^\n  122 |     const park = rows.find(c => c.customer_id === 'cm-test-park-hospital');\n  123 |     expect(park).toBeTruthy();\n  124 |     expect(park.contact_count).toBeGreaterThanOrEqual(3);\n    at /var/www/360lm/tests/customers.spec.js:121:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:13:06.519Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-f511c-tomers-exist-in-customers-v-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 25,
                        "line": 121
                      }
                    },
                    {
                      "workerIndex": 1196,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:121:25",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 25,
                          "line": 121
                        },
                        "snippet": "  119 |     });\n  120 |     const rows = await r.json();\n> 121 |     expect(rows.length).toBeGreaterThanOrEqual(8);\n      |                         ^\n  122 |     const park = rows.find(c => c.customer_id === 'cm-test-park-hospital');\n  123 |     expect(park).toBeTruthy();\n  124 |     expect(park.contact_count).toBeGreaterThanOrEqual(3);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 25,
                            "line": 121
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m8\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  119 |     });\n  120 |     const rows = await r.json();\n> 121 |     expect(rows.length).toBeGreaterThanOrEqual(8);\n      |                         ^\n  122 |     const park = rows.find(c => c.customer_id === 'cm-test-park-hospital');\n  123 |     expect(park).toBeTruthy();\n  124 |     expect(park.contact_count).toBeGreaterThanOrEqual(3);\n    at /var/www/360lm/tests/customers.spec.js:121:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:13:07.499Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-f511c-tomers-exist-in-customers-v-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 25,
                        "line": 121
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-f0f6f25ca696778c0668",
              "file": "customers.spec.js",
              "line": 115,
              "column": 3
            },
            {
              "title": "test leads cover all 4 active statuses",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1197,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 74,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:136:33",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 33,
                          "line": 136
                        },
                        "snippet": "  134 |     const rows = await r.json();\n  135 |     const statuses = new Set(rows.map(r => r.status));\n> 136 |     expect(statuses.has('new')).toBeTruthy();\n      |                                 ^\n  137 |     expect(statuses.has('contacted')).toBeTruthy();\n  138 |     expect(statuses.has('qualified')).toBeTruthy();\n  139 |     expect(statuses.has('disqualified')).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 33,
                            "line": 136
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n\n  134 |     const rows = await r.json();\n  135 |     const statuses = new Set(rows.map(r => r.status));\n> 136 |     expect(statuses.has('new')).toBeTruthy();\n      |                                 ^\n  137 |     expect(statuses.has('contacted')).toBeTruthy();\n  138 |     expect(statuses.has('qualified')).toBeTruthy();\n  139 |     expect(statuses.has('disqualified')).toBeTruthy();\n    at /var/www/360lm/tests/customers.spec.js:136:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:13:08.419Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-fd9b9-cover-all-4-active-statuses-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 33,
                        "line": 136
                      }
                    },
                    {
                      "workerIndex": 1198,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:136:33",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 33,
                          "line": 136
                        },
                        "snippet": "  134 |     const rows = await r.json();\n  135 |     const statuses = new Set(rows.map(r => r.status));\n> 136 |     expect(statuses.has('new')).toBeTruthy();\n      |                                 ^\n  137 |     expect(statuses.has('contacted')).toBeTruthy();\n  138 |     expect(statuses.has('qualified')).toBeTruthy();\n  139 |     expect(statuses.has('disqualified')).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 33,
                            "line": 136
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n\n  134 |     const rows = await r.json();\n  135 |     const statuses = new Set(rows.map(r => r.status));\n> 136 |     expect(statuses.has('new')).toBeTruthy();\n      |                                 ^\n  137 |     expect(statuses.has('contacted')).toBeTruthy();\n  138 |     expect(statuses.has('qualified')).toBeTruthy();\n  139 |     expect(statuses.has('disqualified')).toBeTruthy();\n    at /var/www/360lm/tests/customers.spec.js:136:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:13:09.430Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-fd9b9-cover-all-4-active-statuses-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 33,
                        "line": 136
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-3eadfd1c7c779154f2a5",
              "file": "customers.spec.js",
              "line": 129,
              "column": 3
            },
            {
              "title": "opportunities span all 5 stages",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1199,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 67,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:150:41",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 41,
                          "line": 150
                        },
                        "snippet": "  148 |     const rows = await r.json();\n  149 |     const stages = new Set(rows.map(r => r.stage));\n> 150 |     expect(stages.has('qualification')).toBeTruthy();\n      |                                         ^\n  151 |     expect(stages.has('proposal')).toBeTruthy();\n  152 |     expect(stages.has('negotiation')).toBeTruthy();\n  153 |     expect(stages.has('won')).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 41,
                            "line": 150
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n\n  148 |     const rows = await r.json();\n  149 |     const stages = new Set(rows.map(r => r.stage));\n> 150 |     expect(stages.has('qualification')).toBeTruthy();\n      |                                         ^\n  151 |     expect(stages.has('proposal')).toBeTruthy();\n  152 |     expect(stages.has('negotiation')).toBeTruthy();\n  153 |     expect(stages.has('won')).toBeTruthy();\n    at /var/www/360lm/tests/customers.spec.js:150:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:13:10.332Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-3e407-rtunities-span-all-5-stages-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 41,
                        "line": 150
                      }
                    },
                    {
                      "workerIndex": 1200,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:150:41",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 41,
                          "line": 150
                        },
                        "snippet": "  148 |     const rows = await r.json();\n  149 |     const stages = new Set(rows.map(r => r.stage));\n> 150 |     expect(stages.has('qualification')).toBeTruthy();\n      |                                         ^\n  151 |     expect(stages.has('proposal')).toBeTruthy();\n  152 |     expect(stages.has('negotiation')).toBeTruthy();\n  153 |     expect(stages.has('won')).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 41,
                            "line": 150
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mfalse\u001b[39m\n\n  148 |     const rows = await r.json();\n  149 |     const stages = new Set(rows.map(r => r.stage));\n> 150 |     expect(stages.has('qualification')).toBeTruthy();\n      |                                         ^\n  151 |     expect(stages.has('proposal')).toBeTruthy();\n  152 |     expect(stages.has('negotiation')).toBeTruthy();\n  153 |     expect(stages.has('won')).toBeTruthy();\n    at /var/www/360lm/tests/customers.spec.js:150:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:13:11.347Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-3e407-rtunities-span-all-5-stages-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 41,
                        "line": 150
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-9369b28ede6cd05fbdc5",
              "file": "customers.spec.js",
              "line": 143,
              "column": 3
            },
            {
              "title": "park-hospital 360 reflects offers + opps + comms",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1201,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 83,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:164:15",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 15,
                          "line": 164
                        },
                        "snippet": "  162 |     });\n  163 |     const [s] = await r.json();\n> 164 |     expect(s).toBeTruthy();\n      |               ^\n  165 |     expect(Number(s.opp_count)).toBeGreaterThanOrEqual(2);  // opp-1 + opp-6\n  166 |     expect(Number(s.opp_won)).toBeGreaterThanOrEqual(1);\n  167 |     expect(Number(s.won_value)).toBeGreaterThanOrEqual(520000);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 15,
                            "line": 164
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m\n\n  162 |     });\n  163 |     const [s] = await r.json();\n> 164 |     expect(s).toBeTruthy();\n      |               ^\n  165 |     expect(Number(s.opp_count)).toBeGreaterThanOrEqual(2);  // opp-1 + opp-6\n  166 |     expect(Number(s.opp_won)).toBeGreaterThanOrEqual(1);\n  167 |     expect(Number(s.won_value)).toBeGreaterThanOrEqual(520000);\n    at /var/www/360lm/tests/customers.spec.js:164:15"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:13:12.363Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-4b61e--reflects-offers-opps-comms-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 15,
                        "line": 164
                      }
                    },
                    {
                      "workerIndex": 1202,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:164:15",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 15,
                          "line": 164
                        },
                        "snippet": "  162 |     });\n  163 |     const [s] = await r.json();\n> 164 |     expect(s).toBeTruthy();\n      |               ^\n  165 |     expect(Number(s.opp_count)).toBeGreaterThanOrEqual(2);  // opp-1 + opp-6\n  166 |     expect(Number(s.opp_won)).toBeGreaterThanOrEqual(1);\n  167 |     expect(Number(s.won_value)).toBeGreaterThanOrEqual(520000);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 15,
                            "line": 164
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31mundefined\u001b[39m\n\n  162 |     });\n  163 |     const [s] = await r.json();\n> 164 |     expect(s).toBeTruthy();\n      |               ^\n  165 |     expect(Number(s.opp_count)).toBeGreaterThanOrEqual(2);  // opp-1 + opp-6\n  166 |     expect(Number(s.opp_won)).toBeGreaterThanOrEqual(1);\n  167 |     expect(Number(s.won_value)).toBeGreaterThanOrEqual(520000);\n    at /var/www/360lm/tests/customers.spec.js:164:15"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:13:13.334Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-4b61e--reflects-offers-opps-comms-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 15,
                        "line": 164
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-3b24d9c5a2b1442b475f",
              "file": "customers.spec.js",
              "line": 158,
              "column": 3
            },
            {
              "title": "pipeline_by_owner has harish + pramod rows",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1203,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:178:25",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 25,
                          "line": 178
                        },
                        "snippet": "  176 |     });\n  177 |     const rows = await r.json();\n> 178 |     expect(rows.length).toBe(2);\n      |                         ^\n  179 |     rows.forEach(row => {\n  180 |       expect(Number(row.open_count) + Number(row.won_count) + Number(row.lost_count)).toBeGreaterThan(0);\n  181 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 25,
                            "line": 178
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  176 |     });\n  177 |     const rows = await r.json();\n> 178 |     expect(rows.length).toBe(2);\n      |                         ^\n  179 |     rows.forEach(row => {\n  180 |       expect(Number(row.open_count) + Number(row.won_count) + Number(row.lost_count)).toBeGreaterThan(0);\n  181 |     });\n    at /var/www/360lm/tests/customers.spec.js:178:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:13:14.180Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-0ec8f-wner-has-harish-pramod-rows-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 25,
                        "line": 178
                      }
                    },
                    {
                      "workerIndex": 1204,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/customers.spec.js:178:25",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 25,
                          "line": 178
                        },
                        "snippet": "  176 |     });\n  177 |     const rows = await r.json();\n> 178 |     expect(rows.length).toBe(2);\n      |                         ^\n  179 |     rows.forEach(row => {\n  180 |       expect(Number(row.open_count) + Number(row.won_count) + Number(row.lost_count)).toBeGreaterThan(0);\n  181 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 25,
                            "line": 178
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  176 |     });\n  177 |     const rows = await r.json();\n> 178 |     expect(rows.length).toBe(2);\n      |                         ^\n  179 |     rows.forEach(row => {\n  180 |       expect(Number(row.open_count) + Number(row.won_count) + Number(row.lost_count)).toBeGreaterThan(0);\n  181 |     });\n    at /var/www/360lm/tests/customers.spec.js:178:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:13:15.017Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-0ec8f-wner-has-harish-pramod-rows-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 25,
                        "line": 178
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-78218eba7b00db25a615",
              "file": "customers.spec.js",
              "line": 172,
              "column": 3
            }
          ]
        },
        {
          "title": "Customer Management — Rendered UI (happy path)",
          "file": "customers.spec.js",
          "line": 189,
          "column": 6,
          "specs": [
            {
              "title": "list renders and search filters live",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 198,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5267,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n\n    at /var/www/360lm/tests/customers.spec.js:193:43",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 43,
                          "line": 193
                        },
                        "snippet": "  191 |     await injectSession(page);\n  192 |     await page.goto(BASE_URL);\n> 193 |     await expect(page.locator('#s-list')).toBeVisible();\n      |                                           ^\n  194 |     // tile shows >=8 customers\n  195 |     await expect(page.locator('#t-total')).not.toHaveText('—');\n  196 |     await expect.poll(async () => Number(await page.locator('#t-total').textContent())).toBeGreaterThanOrEqual(8);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 43,
                            "line": 193
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n\n\n  191 |     await injectSession(page);\n  192 |     await page.goto(BASE_URL);\n> 193 |     await expect(page.locator('#s-list')).toBeVisible();\n      |                                           ^\n  194 |     // tile shows >=8 customers\n  195 |     await expect(page.locator('#t-total')).not.toHaveText('—');\n  196 |     await expect.poll(async () => Number(await page.locator('#t-total').textContent())).toBeGreaterThanOrEqual(8);\n    at /var/www/360lm/tests/customers.spec.js:193:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:37.147Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-d1127-ers-and-search-filters-live-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-d1127-ers-and-search-filters-live-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 43,
                        "line": 193
                      }
                    },
                    {
                      "workerIndex": 199,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5269,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n\n    at /var/www/360lm/tests/customers.spec.js:193:43",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 43,
                          "line": 193
                        },
                        "snippet": "  191 |     await injectSession(page);\n  192 |     await page.goto(BASE_URL);\n> 193 |     await expect(page.locator('#s-list')).toBeVisible();\n      |                                           ^\n  194 |     // tile shows >=8 customers\n  195 |     await expect(page.locator('#t-total')).not.toHaveText('—');\n  196 |     await expect.poll(async () => Number(await page.locator('#t-total').textContent())).toBeGreaterThanOrEqual(8);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 43,
                            "line": 193
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n\n\n  191 |     await injectSession(page);\n  192 |     await page.goto(BASE_URL);\n> 193 |     await expect(page.locator('#s-list')).toBeVisible();\n      |                                           ^\n  194 |     // tile shows >=8 customers\n  195 |     await expect(page.locator('#t-total')).not.toHaveText('—');\n  196 |     await expect.poll(async () => Number(await page.locator('#t-total').textContent())).toBeGreaterThanOrEqual(8);\n    at /var/www/360lm/tests/customers.spec.js:193:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:13:43.389Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-d1127-ers-and-search-filters-live-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-d1127-ers-and-search-filters-live-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 43,
                        "line": 193
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-0f501e2bc02906c2d777",
              "file": "customers.spec.js",
              "line": 190,
              "column": 3
            },
            {
              "title": "type chip filters to Individual only",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 200,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 209
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  207 |     await injectSession(page);\n  208 |     await page.goto(BASE_URL);\n> 209 |     await page.waitForSelector('.c-row');\n      |                ^\n  210 |     await page.click('.chip[data-type=\"individual\"]');\n  211 |     await page.waitForTimeout(150);\n  212 |     const badges = await page.locator('.c-row-badge').allTextContents();\n    at /var/www/360lm/tests/customers.spec.js:209:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:13:49.707Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-79315--filters-to-Individual-only-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-79315--filters-to-Individual-only-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 201,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30115,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 209
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  207 |     await injectSession(page);\n  208 |     await page.goto(BASE_URL);\n> 209 |     await page.waitForSelector('.c-row');\n      |                ^\n  210 |     await page.click('.chip[data-type=\"individual\"]');\n  211 |     await page.waitForTimeout(150);\n  212 |     const badges = await page.locator('.c-row-badge').allTextContents();\n    at /var/www/360lm/tests/customers.spec.js:209:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:14:20.840Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-79315--filters-to-Individual-only-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-79315--filters-to-Individual-only-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-a4d83dc68af4a0ad5dbd",
              "file": "customers.spec.js",
              "line": 206,
              "column": 3
            },
            {
              "title": "detail screen opens with all 7 tabs",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 202,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 220
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  218 |     await injectSession(page);\n  219 |     await page.goto(BASE_URL);\n> 220 |     await page.waitForSelector('.c-row');\n      |                ^\n  221 |     await page.fill('#search-q', 'park');\n  222 |     await page.waitForTimeout(300);\n  223 |     await page.locator('.c-row').first().click();\n    at /var/www/360lm/tests/customers.spec.js:220:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:14:51.978Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-1432d-creen-opens-with-all-7-tabs-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-1432d-creen-opens-with-all-7-tabs-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 203,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30110,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 220
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  218 |     await injectSession(page);\n  219 |     await page.goto(BASE_URL);\n> 220 |     await page.waitForSelector('.c-row');\n      |                ^\n  221 |     await page.fill('#search-q', 'park');\n  222 |     await page.waitForTimeout(300);\n  223 |     await page.locator('.c-row').first().click();\n    at /var/www/360lm/tests/customers.spec.js:220:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:15:23.148Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-1432d-creen-opens-with-all-7-tabs-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-1432d-creen-opens-with-all-7-tabs-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-c87aacd7b2b3cf7dc34f",
              "file": "customers.spec.js",
              "line": 217,
              "column": 3
            },
            {
              "title": "360 card renders on Info tab",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 204,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30073,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 233
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  231 |     await injectSession(page);\n  232 |     await page.goto(BASE_URL);\n> 233 |     await page.waitForSelector('.c-row');\n      |                ^\n  234 |     await page.fill('#search-q', 'park');\n  235 |     await page.waitForTimeout(300);\n  236 |     await page.locator('.c-row').first().click();\n    at /var/www/360lm/tests/customers.spec.js:233:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:15:54.229Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-5d4b7-60-card-renders-on-Info-tab-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-5d4b7-60-card-renders-on-Info-tab-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 205,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30119,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 233
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  231 |     await injectSession(page);\n  232 |     await page.goto(BASE_URL);\n> 233 |     await page.waitForSelector('.c-row');\n      |                ^\n  234 |     await page.fill('#search-q', 'park');\n  235 |     await page.waitForTimeout(300);\n  236 |     await page.locator('.c-row').first().click();\n    at /var/www/360lm/tests/customers.spec.js:233:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:16:25.315Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-5d4b7-60-card-renders-on-Info-tab-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-5d4b7-60-card-renders-on-Info-tab-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-de3e7a951457b750b548",
              "file": "customers.spec.js",
              "line": 230,
              "column": 3
            },
            {
              "title": "Pipeline screen opens with leads kanban + summary tiles",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 206,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30073,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 246
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-pipeline') to be visible\u001b[22m\n\n\n  244 |     await injectSession(page);\n  245 |     await page.goto(BASE_URL);\n> 246 |     await page.waitForSelector('#nav-pipeline');\n      |                ^\n  247 |     await page.click('#nav-pipeline');\n  248 |     await expect(page.locator('#s-pipeline')).toBeVisible();\n  249 |     await expect(page.locator('#kanban-leads')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:246:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:16:56.392Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8875b--leads-kanban-summary-tiles-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8875b--leads-kanban-summary-tiles-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 207,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30097,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 246
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-pipeline') to be visible\u001b[22m\n\n\n  244 |     await injectSession(page);\n  245 |     await page.goto(BASE_URL);\n> 246 |     await page.waitForSelector('#nav-pipeline');\n      |                ^\n  247 |     await page.click('#nav-pipeline');\n  248 |     await expect(page.locator('#s-pipeline')).toBeVisible();\n  249 |     await expect(page.locator('#kanban-leads')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:246:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:17:27.504Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8875b--leads-kanban-summary-tiles-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8875b--leads-kanban-summary-tiles-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-97e24ff4fe3b0c980248",
              "file": "customers.spec.js",
              "line": 243,
              "column": 3
            },
            {
              "title": "owner filter chips render and filter Leads kanban",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 208,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30117,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 262
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-pipeline') to be visible\u001b[22m\n\n\n  260 |     await injectSession(page);\n  261 |     await page.goto(BASE_URL);\n> 262 |     await page.waitForSelector('#nav-pipeline');\n      |                ^\n  263 |     await page.click('#nav-pipeline');\n  264 |     await page.waitForTimeout(500);\n  265 |     await expect(page.locator('#owner-chips')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:262:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:17:58.693Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-94975-der-and-filter-Leads-kanban-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-94975-der-and-filter-Leads-kanban-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 209,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30085,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 262
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-pipeline') to be visible\u001b[22m\n\n\n  260 |     await injectSession(page);\n  261 |     await page.goto(BASE_URL);\n> 262 |     await page.waitForSelector('#nav-pipeline');\n      |                ^\n  263 |     await page.click('#nav-pipeline');\n  264 |     await page.waitForTimeout(500);\n  265 |     await expect(page.locator('#owner-chips')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:262:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:18:29.804Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-94975-der-and-filter-Leads-kanban-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-94975-der-and-filter-Leads-kanban-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-c3790719bb2ac1c5693a",
              "file": "customers.spec.js",
              "line": 259,
              "column": 3
            },
            {
              "title": "Add-customer form has Save button and chip toggle",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 210,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30085,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 280
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  278 |     await injectSession(page);\n  279 |     await page.goto(BASE_URL);\n> 280 |     await page.click('#nav-add');\n      |                ^\n  281 |     await expect(page.locator('#s-form')).toBeVisible();\n  282 |     await expect(page.locator('#f-save-btn')).toBeVisible();\n  283 |     await expect(page.locator('#s-form .chip[data-ftype=\"business\"]')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:280:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:19:01.051Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-e37c0-Save-button-and-chip-toggle-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-e37c0-Save-button-and-chip-toggle-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 211,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30278,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 280
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  278 |     await injectSession(page);\n  279 |     await page.goto(BASE_URL);\n> 280 |     await page.click('#nav-add');\n      |                ^\n  281 |     await expect(page.locator('#s-form')).toBeVisible();\n  282 |     await expect(page.locator('#f-save-btn')).toBeVisible();\n  283 |     await expect(page.locator('#s-form .chip[data-ftype=\"business\"]')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:280:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:19:32.141Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-e37c0-Save-button-and-chip-toggle-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-e37c0-Save-button-and-chip-toggle-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-8523fb63e45d3ba8c863",
              "file": "customers.spec.js",
              "line": 277,
              "column": 3
            },
            {
              "title": "list renders and search filters live",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1205,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5270,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n\n    at /var/www/360lm/tests/customers.spec.js:193:43",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 43,
                          "line": 193
                        },
                        "snippet": "  191 |     await injectSession(page);\n  192 |     await page.goto(BASE_URL);\n> 193 |     await expect(page.locator('#s-list')).toBeVisible();\n      |                                           ^\n  194 |     // tile shows >=8 customers\n  195 |     await expect(page.locator('#t-total')).not.toHaveText('—');\n  196 |     await expect.poll(async () => Number(await page.locator('#t-total').textContent())).toBeGreaterThanOrEqual(8);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 43,
                            "line": 193
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n\n\n  191 |     await injectSession(page);\n  192 |     await page.goto(BASE_URL);\n> 193 |     await expect(page.locator('#s-list')).toBeVisible();\n      |                                           ^\n  194 |     // tile shows >=8 customers\n  195 |     await expect(page.locator('#t-total')).not.toHaveText('—');\n  196 |     await expect.poll(async () => Number(await page.locator('#t-total').textContent())).toBeGreaterThanOrEqual(8);\n    at /var/www/360lm/tests/customers.spec.js:193:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:13:15.874Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-d1127-ers-and-search-filters-live-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-d1127-ers-and-search-filters-live-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 43,
                        "line": 193
                      }
                    },
                    {
                      "workerIndex": 1206,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5285,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n\n    at /var/www/360lm/tests/customers.spec.js:193:43",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 43,
                          "line": 193
                        },
                        "snippet": "  191 |     await injectSession(page);\n  192 |     await page.goto(BASE_URL);\n> 193 |     await expect(page.locator('#s-list')).toBeVisible();\n      |                                           ^\n  194 |     // tile shows >=8 customers\n  195 |     await expect(page.locator('#t-total')).not.toHaveText('—');\n  196 |     await expect.poll(async () => Number(await page.locator('#t-total').textContent())).toBeGreaterThanOrEqual(8);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 43,
                            "line": 193
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list')\u001b[22m\n\n\n  191 |     await injectSession(page);\n  192 |     await page.goto(BASE_URL);\n> 193 |     await expect(page.locator('#s-list')).toBeVisible();\n      |                                           ^\n  194 |     // tile shows >=8 customers\n  195 |     await expect(page.locator('#t-total')).not.toHaveText('—');\n  196 |     await expect.poll(async () => Number(await page.locator('#t-total').textContent())).toBeGreaterThanOrEqual(8);\n    at /var/www/360lm/tests/customers.spec.js:193:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:13:22.126Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-d1127-ers-and-search-filters-live-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-d1127-ers-and-search-filters-live-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 43,
                        "line": 193
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-5e61d0fd407660907eaf",
              "file": "customers.spec.js",
              "line": 190,
              "column": 3
            },
            {
              "title": "type chip filters to Individual only",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1207,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 209
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  207 |     await injectSession(page);\n  208 |     await page.goto(BASE_URL);\n> 209 |     await page.waitForSelector('.c-row');\n      |                ^\n  210 |     await page.click('.chip[data-type=\"individual\"]');\n  211 |     await page.waitForTimeout(150);\n  212 |     const badges = await page.locator('.c-row-badge').allTextContents();\n    at /var/www/360lm/tests/customers.spec.js:209:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:13:28.335Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-79315--filters-to-Individual-only-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-79315--filters-to-Individual-only-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1208,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30112,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 209
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  207 |     await injectSession(page);\n  208 |     await page.goto(BASE_URL);\n> 209 |     await page.waitForSelector('.c-row');\n      |                ^\n  210 |     await page.click('.chip[data-type=\"individual\"]');\n  211 |     await page.waitForTimeout(150);\n  212 |     const badges = await page.locator('.c-row-badge').allTextContents();\n    at /var/www/360lm/tests/customers.spec.js:209:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:13:59.348Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-79315--filters-to-Individual-only-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-79315--filters-to-Individual-only-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-df661339ff2faf3e4fa4",
              "file": "customers.spec.js",
              "line": 206,
              "column": 3
            },
            {
              "title": "detail screen opens with all 7 tabs",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1209,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30162,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 220
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  218 |     await injectSession(page);\n  219 |     await page.goto(BASE_URL);\n> 220 |     await page.waitForSelector('.c-row');\n      |                ^\n  221 |     await page.fill('#search-q', 'park');\n  222 |     await page.waitForTimeout(300);\n  223 |     await page.locator('.c-row').first().click();\n    at /var/www/360lm/tests/customers.spec.js:220:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:14:30.634Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-1432d-creen-opens-with-all-7-tabs-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-1432d-creen-opens-with-all-7-tabs-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1210,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30071,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 220
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  218 |     await injectSession(page);\n  219 |     await page.goto(BASE_URL);\n> 220 |     await page.waitForSelector('.c-row');\n      |                ^\n  221 |     await page.fill('#search-q', 'park');\n  222 |     await page.waitForTimeout(300);\n  223 |     await page.locator('.c-row').first().click();\n    at /var/www/360lm/tests/customers.spec.js:220:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:15:03.511Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-1432d-creen-opens-with-all-7-tabs-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-1432d-creen-opens-with-all-7-tabs-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-52b5bff5b1251172fecd",
              "file": "customers.spec.js",
              "line": 217,
              "column": 3
            },
            {
              "title": "360 card renders on Info tab",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1211,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30137,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 233
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  231 |     await injectSession(page);\n  232 |     await page.goto(BASE_URL);\n> 233 |     await page.waitForSelector('.c-row');\n      |                ^\n  234 |     await page.fill('#search-q', 'park');\n  235 |     await page.waitForTimeout(300);\n  236 |     await page.locator('.c-row').first().click();\n    at /var/www/360lm/tests/customers.spec.js:233:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:15:34.762Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-5d4b7-60-card-renders-on-Info-tab-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-5d4b7-60-card-renders-on-Info-tab-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1212,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30087,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 233
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.c-row') to be visible\u001b[22m\n\n\n  231 |     await injectSession(page);\n  232 |     await page.goto(BASE_URL);\n> 233 |     await page.waitForSelector('.c-row');\n      |                ^\n  234 |     await page.fill('#search-q', 'park');\n  235 |     await page.waitForTimeout(300);\n  236 |     await page.locator('.c-row').first().click();\n    at /var/www/360lm/tests/customers.spec.js:233:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:16:06.076Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-5d4b7-60-card-renders-on-Info-tab-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-5d4b7-60-card-renders-on-Info-tab-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-aa0ba69d6b3a53f0124e",
              "file": "customers.spec.js",
              "line": 230,
              "column": 3
            },
            {
              "title": "Pipeline screen opens with leads kanban + summary tiles",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1213,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30109,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 246
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-pipeline') to be visible\u001b[22m\n\n\n  244 |     await injectSession(page);\n  245 |     await page.goto(BASE_URL);\n> 246 |     await page.waitForSelector('#nav-pipeline');\n      |                ^\n  247 |     await page.click('#nav-pipeline');\n  248 |     await expect(page.locator('#s-pipeline')).toBeVisible();\n  249 |     await expect(page.locator('#kanban-leads')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:246:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:16:37.245Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8875b--leads-kanban-summary-tiles-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8875b--leads-kanban-summary-tiles-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1214,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30080,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 246
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-pipeline') to be visible\u001b[22m\n\n\n  244 |     await injectSession(page);\n  245 |     await page.goto(BASE_URL);\n> 246 |     await page.waitForSelector('#nav-pipeline');\n      |                ^\n  247 |     await page.click('#nav-pipeline');\n  248 |     await expect(page.locator('#s-pipeline')).toBeVisible();\n  249 |     await expect(page.locator('#kanban-leads')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:246:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:17:08.383Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8875b--leads-kanban-summary-tiles-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-8875b--leads-kanban-summary-tiles-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-fc75d2373c9b556ae638",
              "file": "customers.spec.js",
              "line": 243,
              "column": 3
            },
            {
              "title": "owner filter chips render and filter Leads kanban",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1215,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30109,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 262
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-pipeline') to be visible\u001b[22m\n\n\n  260 |     await injectSession(page);\n  261 |     await page.goto(BASE_URL);\n> 262 |     await page.waitForSelector('#nav-pipeline');\n      |                ^\n  263 |     await page.click('#nav-pipeline');\n  264 |     await page.waitForTimeout(500);\n  265 |     await expect(page.locator('#owner-chips')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:262:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:17:39.522Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-94975-der-and-filter-Leads-kanban-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-94975-der-and-filter-Leads-kanban-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1216,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30103,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 262
                          },
                          "message": "Error: page.waitForSelector: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-pipeline') to be visible\u001b[22m\n\n\n  260 |     await injectSession(page);\n  261 |     await page.goto(BASE_URL);\n> 262 |     await page.waitForSelector('#nav-pipeline');\n      |                ^\n  263 |     await page.click('#nav-pipeline');\n  264 |     await page.waitForTimeout(500);\n  265 |     await expect(page.locator('#owner-chips')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:262:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:18:10.691Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-94975-der-and-filter-Leads-kanban-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-94975-der-and-filter-Leads-kanban-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-640b66d1c7e96fdb7c96",
              "file": "customers.spec.js",
              "line": 259,
              "column": 3
            },
            {
              "title": "Add-customer form has Save button and chip toggle",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1217,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30073,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 280
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  278 |     await injectSession(page);\n  279 |     await page.goto(BASE_URL);\n> 280 |     await page.click('#nav-add');\n      |                ^\n  281 |     await expect(page.locator('#s-form')).toBeVisible();\n  282 |     await expect(page.locator('#f-save-btn')).toBeVisible();\n  283 |     await expect(page.locator('#s-form .chip[data-ftype=\"business\"]')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:280:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:18:41.748Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-e37c0-Save-button-and-chip-toggle-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-e37c0-Save-button-and-chip-toggle-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1218,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 280
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  278 |     await injectSession(page);\n  279 |     await page.goto(BASE_URL);\n> 280 |     await page.click('#nav-add');\n      |                ^\n  281 |     await expect(page.locator('#s-form')).toBeVisible();\n  282 |     await expect(page.locator('#f-save-btn')).toBeVisible();\n  283 |     await expect(page.locator('#s-form .chip[data-ftype=\"business\"]')).toBeVisible();\n    at /var/www/360lm/tests/customers.spec.js:280:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:19:12.760Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-e37c0-Save-button-and-chip-toggle-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-e37c0-Save-button-and-chip-toggle-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-02477885d4bcfff93349",
              "file": "customers.spec.js",
              "line": 277,
              "column": 3
            }
          ]
        },
        {
          "title": "Customer Management — UI CRUD",
          "file": "customers.spec.js",
          "line": 291,
          "column": 6,
          "specs": [
            {
              "title": "create customer via form, appears in list, delete via DB",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 212,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30094,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 297
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  295 |     await injectSession(page);\n  296 |     await page.goto(BASE_URL);\n> 297 |     await page.click('#nav-add');\n      |                ^\n  298 |     await page.fill('#f-name', testName);\n  299 |     await page.fill('#f-phone', '9999000111');\n  300 |     await page.fill('#f-industry', 'Hospitality');\n    at /var/www/360lm/tests/customers.spec.js:297:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:20:05.263Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-55ec0-pears-in-list-delete-via-DB-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-55ec0-pears-in-list-delete-via-DB-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 213,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30100,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 297
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  295 |     await injectSession(page);\n  296 |     await page.goto(BASE_URL);\n> 297 |     await page.click('#nav-add');\n      |                ^\n  298 |     await page.fill('#f-name', testName);\n  299 |     await page.fill('#f-phone', '9999000111');\n  300 |     await page.fill('#f-industry', 'Hospitality');\n    at /var/www/360lm/tests/customers.spec.js:297:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:20:36.807Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-55ec0-pears-in-list-delete-via-DB-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-55ec0-pears-in-list-delete-via-DB-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-b1116c5cf3c56ac6b016",
              "file": "customers.spec.js",
              "line": 294,
              "column": 3
            },
            {
              "title": "empty form save shows validation toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 214,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 315
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  313 |     await injectSession(page);\n  314 |     await page.goto(BASE_URL);\n> 315 |     await page.click('#nav-add');\n      |                ^\n  316 |     await page.evaluate(() => saveForm());\n  317 |     // Form should still be visible — validation prevented save\n  318 |     await page.waitForTimeout(400);\n    at /var/www/360lm/tests/customers.spec.js:315:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:21:07.833Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-bbc63-save-shows-validation-toast-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-bbc63-save-shows-validation-toast-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 215,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30102,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 315
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  313 |     await injectSession(page);\n  314 |     await page.goto(BASE_URL);\n> 315 |     await page.click('#nav-add');\n      |                ^\n  316 |     await page.evaluate(() => saveForm());\n  317 |     // Form should still be visible — validation prevented save\n  318 |     await page.waitForTimeout(400);\n    at /var/www/360lm/tests/customers.spec.js:315:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:21:38.953Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-bbc63-save-shows-validation-toast-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-bbc63-save-shows-validation-toast-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-2881040e5d8a52bd89d1",
              "file": "customers.spec.js",
              "line": 312,
              "column": 3
            },
            {
              "title": "create customer via form, appears in list, delete via DB",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1219,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 297
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  295 |     await injectSession(page);\n  296 |     await page.goto(BASE_URL);\n> 297 |     await page.click('#nav-add');\n      |                ^\n  298 |     await page.fill('#f-name', testName);\n  299 |     await page.fill('#f-phone', '9999000111');\n  300 |     await page.fill('#f-industry', 'Hospitality');\n    at /var/www/360lm/tests/customers.spec.js:297:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:19:43.761Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-55ec0-pears-in-list-delete-via-DB-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-55ec0-pears-in-list-delete-via-DB-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1220,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30094,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 297
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  295 |     await injectSession(page);\n  296 |     await page.goto(BASE_URL);\n> 297 |     await page.click('#nav-add');\n      |                ^\n  298 |     await page.fill('#f-name', testName);\n  299 |     await page.fill('#f-phone', '9999000111');\n  300 |     await page.fill('#f-industry', 'Hospitality');\n    at /var/www/360lm/tests/customers.spec.js:297:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:20:14.916Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-55ec0-pears-in-list-delete-via-DB-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-55ec0-pears-in-list-delete-via-DB-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-7290cbf6939843677559",
              "file": "customers.spec.js",
              "line": 294,
              "column": 3
            },
            {
              "title": "empty form save shows validation toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1221,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30073,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 315
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  313 |     await injectSession(page);\n  314 |     await page.goto(BASE_URL);\n> 315 |     await page.click('#nav-add');\n      |                ^\n  316 |     await page.evaluate(() => saveForm());\n  317 |     // Form should still be visible — validation prevented save\n  318 |     await page.waitForTimeout(400);\n    at /var/www/360lm/tests/customers.spec.js:315:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:20:45.977Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-bbc63-save-shows-validation-toast-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-bbc63-save-shows-validation-toast-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1222,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30090,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 16,
                            "line": 315
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-add')\u001b[22m\n\n\n  313 |     await injectSession(page);\n  314 |     await page.goto(BASE_URL);\n> 315 |     await page.click('#nav-add');\n      |                ^\n  316 |     await page.evaluate(() => saveForm());\n  317 |     // Form should still be visible — validation prevented save\n  318 |     await page.waitForTimeout(400);\n    at /var/www/360lm/tests/customers.spec.js:315:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:21:17.003Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-bbc63-save-shows-validation-toast-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-bbc63-save-shows-validation-toast-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-da76dadfb4e2f8f72e20",
              "file": "customers.spec.js",
              "line": 312,
              "column": 3
            }
          ]
        },
        {
          "title": "Customer Management — Access",
          "file": "customers.spec.js",
          "line": 329,
          "column": 6,
          "specs": [
            {
              "title": "user without grant sees restricted screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 216,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6072,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/customers.spec.js:335:58",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 58,
                          "line": 335
                        },
                        "snippet": "  333 |     await page.goto(BASE_URL);\n  334 |     await page.waitForTimeout(800);\n> 335 |     await expect(page.locator('text=Access Restricted')).toBeVisible();\n      |                                                          ^\n  336 |   });\n  337 | });\n  338 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 58,
                            "line": 335
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  333 |     await page.goto(BASE_URL);\n  334 |     await page.waitForTimeout(800);\n> 335 |     await expect(page.locator('text=Access Restricted')).toBeVisible();\n      |                                                          ^\n  336 |   });\n  337 | });\n  338 |\n    at /var/www/360lm/tests/customers.spec.js:335:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:10.097Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-a618d-rant-sees-restricted-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-a618d-rant-sees-restricted-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 58,
                        "line": 335
                      }
                    },
                    {
                      "workerIndex": 217,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6054,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/customers.spec.js:335:58",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 58,
                          "line": 335
                        },
                        "snippet": "  333 |     await page.goto(BASE_URL);\n  334 |     await page.waitForTimeout(800);\n> 335 |     await expect(page.locator('text=Access Restricted')).toBeVisible();\n      |                                                          ^\n  336 |   });\n  337 | });\n  338 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 58,
                            "line": 335
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  333 |     await page.goto(BASE_URL);\n  334 |     await page.waitForTimeout(800);\n> 335 |     await expect(page.locator('text=Access Restricted')).toBeVisible();\n      |                                                          ^\n  336 |   });\n  337 | });\n  338 |\n    at /var/www/360lm/tests/customers.spec.js:335:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:22:17.182Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-a618d-rant-sees-restricted-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-a618d-rant-sees-restricted-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 58,
                        "line": 335
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-55e9d24769b795b1ce74",
              "file": "customers.spec.js",
              "line": 330,
              "column": 3
            },
            {
              "title": "user without grant sees restricted screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1223,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6089,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/customers.spec.js:335:58",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 58,
                          "line": 335
                        },
                        "snippet": "  333 |     await page.goto(BASE_URL);\n  334 |     await page.waitForTimeout(800);\n> 335 |     await expect(page.locator('text=Access Restricted')).toBeVisible();\n      |                                                          ^\n  336 |   });\n  337 | });\n  338 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 58,
                            "line": 335
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  333 |     await page.goto(BASE_URL);\n  334 |     await page.waitForTimeout(800);\n> 335 |     await expect(page.locator('text=Access Restricted')).toBeVisible();\n      |                                                          ^\n  336 |   });\n  337 | });\n  338 |\n    at /var/www/360lm/tests/customers.spec.js:335:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:21:48.209Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-a618d-rant-sees-restricted-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-a618d-rant-sees-restricted-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 58,
                        "line": 335
                      }
                    },
                    {
                      "workerIndex": 1224,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6341,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/customers.spec.js:335:58",
                        "location": {
                          "file": "/var/www/360lm/tests/customers.spec.js",
                          "column": 58,
                          "line": 335
                        },
                        "snippet": "  333 |     await page.goto(BASE_URL);\n  334 |     await page.waitForTimeout(800);\n> 335 |     await expect(page.locator('text=Access Restricted')).toBeVisible();\n      |                                                          ^\n  336 |   });\n  337 | });\n  338 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/customers.spec.js",
                            "column": 58,
                            "line": 335
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  333 |     await page.goto(BASE_URL);\n  334 |     await page.waitForTimeout(800);\n> 335 |     await expect(page.locator('text=Access Restricted')).toBeVisible();\n      |                                                          ^\n  336 |   });\n  337 | });\n  338 |\n    at /var/www/360lm/tests/customers.spec.js:335:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:21:55.290Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-a618d-rant-sees-restricted-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/customers-Customer-Managem-a618d-rant-sees-restricted-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/customers.spec.js",
                        "column": 58,
                        "line": 335
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7ab72d16adb42747d672-a59ceb3647a9aec83440",
              "file": "customers.spec.js",
              "line": 330,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "debug_tour.spec.js",
      "file": "debug_tour.spec.js",
      "column": 0,
      "line": 0,
      "specs": [
        {
          "title": "debug tour-pg Excel upload",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 218,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 1807,
                  "error": {
                    "message": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'",
                    "stack": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'\n    at /var/www/360lm/tests/debug_tour.spec.js:24:3",
                    "location": {
                      "file": "/var/www/360lm/tests/debug_tour.spec.js",
                      "column": 3,
                      "line": 24
                    },
                    "snippet": "  22 |\n  23 |   // Upload the CSV via file input\n> 24 |   await page.locator('#excel-file').setInputFiles('/tmp/test_stores.csv');\n     |   ^\n  25 |   await page.waitForTimeout(1000);\n  26 |\n  27 |   const previewVisible = await page.locator('#parsed-preview').isVisible();"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/debug_tour.spec.js",
                        "column": 3,
                        "line": 24
                      },
                      "message": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'\n\n  22 |\n  23 |   // Upload the CSV via file input\n> 24 |   await page.locator('#excel-file').setInputFiles('/tmp/test_stores.csv');\n     |   ^\n  25 |   await page.waitForTimeout(1000);\n  26 |\n  27 |   const previewVisible = await page.locator('#parsed-preview').isVisible();\n    at /var/www/360lm/tests/debug_tour.spec.js:24:3"
                    }
                  ],
                  "stdout": [
                    {
                      "text": "URL: https://srv1111289.hstgr.cloud/tour-pg/\n"
                    },
                    {
                      "text": "XLSX loaded: \u001b[33mfalse\u001b[39m\n"
                    }
                  ],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T19:22:24.186Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/debug_tour-debug-tour-pg-Excel-upload-android-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/debug_tour-debug-tour-pg-Excel-upload-android-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/debug_tour.spec.js",
                    "column": 3,
                    "line": 24
                  }
                },
                {
                  "workerIndex": 219,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 1800,
                  "error": {
                    "message": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'",
                    "stack": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'\n    at /var/www/360lm/tests/debug_tour.spec.js:24:3",
                    "location": {
                      "file": "/var/www/360lm/tests/debug_tour.spec.js",
                      "column": 3,
                      "line": 24
                    },
                    "snippet": "  22 |\n  23 |   // Upload the CSV via file input\n> 24 |   await page.locator('#excel-file').setInputFiles('/tmp/test_stores.csv');\n     |   ^\n  25 |   await page.waitForTimeout(1000);\n  26 |\n  27 |   const previewVisible = await page.locator('#parsed-preview').isVisible();"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/debug_tour.spec.js",
                        "column": 3,
                        "line": 24
                      },
                      "message": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'\n\n  22 |\n  23 |   // Upload the CSV via file input\n> 24 |   await page.locator('#excel-file').setInputFiles('/tmp/test_stores.csv');\n     |   ^\n  25 |   await page.waitForTimeout(1000);\n  26 |\n  27 |   const previewVisible = await page.locator('#parsed-preview').isVisible();\n    at /var/www/360lm/tests/debug_tour.spec.js:24:3"
                    }
                  ],
                  "stdout": [
                    {
                      "text": "URL: https://srv1111289.hstgr.cloud/tour-pg/\n"
                    },
                    {
                      "text": "XLSX loaded: \u001b[33mfalse\u001b[39m\n"
                    }
                  ],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T19:22:27.032Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/debug_tour-debug-tour-pg-Excel-upload-android-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/debug_tour-debug-tour-pg-Excel-upload-android-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/debug_tour.spec.js",
                    "column": 3,
                    "line": 24
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "616efaf6657916c75e81-7ce3b8c226ec37f448cd",
          "file": "debug_tour.spec.js",
          "line": 4,
          "column": 1
        },
        {
          "title": "debug tour-pg Excel upload",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1225,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 1980,
                  "error": {
                    "message": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'",
                    "stack": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'\n    at /var/www/360lm/tests/debug_tour.spec.js:24:3",
                    "location": {
                      "file": "/var/www/360lm/tests/debug_tour.spec.js",
                      "column": 3,
                      "line": 24
                    },
                    "snippet": "  22 |\n  23 |   // Upload the CSV via file input\n> 24 |   await page.locator('#excel-file').setInputFiles('/tmp/test_stores.csv');\n     |   ^\n  25 |   await page.waitForTimeout(1000);\n  26 |\n  27 |   const previewVisible = await page.locator('#parsed-preview').isVisible();"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/debug_tour.spec.js",
                        "column": 3,
                        "line": 24
                      },
                      "message": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'\n\n  22 |\n  23 |   // Upload the CSV via file input\n> 24 |   await page.locator('#excel-file').setInputFiles('/tmp/test_stores.csv');\n     |   ^\n  25 |   await page.waitForTimeout(1000);\n  26 |\n  27 |   const previewVisible = await page.locator('#parsed-preview').isVisible();\n    at /var/www/360lm/tests/debug_tour.spec.js:24:3"
                    }
                  ],
                  "stdout": [
                    {
                      "text": "URL: https://srv1111289.hstgr.cloud/tour-pg/\n"
                    },
                    {
                      "text": "XLSX loaded: \u001b[33mfalse\u001b[39m\n"
                    }
                  ],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T22:22:03.127Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/debug_tour-debug-tour-pg-Excel-upload-desktop-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/debug_tour-debug-tour-pg-Excel-upload-desktop-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/debug_tour.spec.js",
                    "column": 3,
                    "line": 24
                  }
                },
                {
                  "workerIndex": 1226,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 1804,
                  "error": {
                    "message": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'",
                    "stack": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'\n    at /var/www/360lm/tests/debug_tour.spec.js:24:3",
                    "location": {
                      "file": "/var/www/360lm/tests/debug_tour.spec.js",
                      "column": 3,
                      "line": 24
                    },
                    "snippet": "  22 |\n  23 |   // Upload the CSV via file input\n> 24 |   await page.locator('#excel-file').setInputFiles('/tmp/test_stores.csv');\n     |   ^\n  25 |   await page.waitForTimeout(1000);\n  26 |\n  27 |   const previewVisible = await page.locator('#parsed-preview').isVisible();"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/debug_tour.spec.js",
                        "column": 3,
                        "line": 24
                      },
                      "message": "Error: ENOENT: no such file or directory, stat '/tmp/test_stores.csv'\n\n  22 |\n  23 |   // Upload the CSV via file input\n> 24 |   await page.locator('#excel-file').setInputFiles('/tmp/test_stores.csv');\n     |   ^\n  25 |   await page.waitForTimeout(1000);\n  26 |\n  27 |   const previewVisible = await page.locator('#parsed-preview').isVisible();\n    at /var/www/360lm/tests/debug_tour.spec.js:24:3"
                    }
                  ],
                  "stdout": [
                    {
                      "text": "URL: https://srv1111289.hstgr.cloud/tour-pg/\n"
                    },
                    {
                      "text": "XLSX loaded: \u001b[33mfalse\u001b[39m\n"
                    }
                  ],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T22:22:06.338Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/debug_tour-debug-tour-pg-Excel-upload-desktop-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/debug_tour-debug-tour-pg-Excel-upload-desktop-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/debug_tour.spec.js",
                    "column": 3,
                    "line": 24
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "616efaf6657916c75e81-0f909bdfea04b2dee729",
          "file": "debug_tour.spec.js",
          "line": 4,
          "column": 1
        }
      ]
    },
    {
      "title": "dispatch.spec.js",
      "file": "dispatch.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Dispatch — Smoke",
          "file": "dispatch.spec.js",
          "line": 9,
          "column": 6,
          "specs": [
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1549,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:29.875Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-63b703f798c6b89c1844",
              "file": "dispatch.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 181,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:31.653Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-0abf5d31404d250a90af",
              "file": "dispatch.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 229,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:31.871Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-37601325b18bf433a5b0",
              "file": "dispatch.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1586,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:09.270Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-79724089e7cb2fee990f",
              "file": "dispatch.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 211,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:11.046Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-8c4b10c8bbd0540d96ee",
              "file": "dispatch.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 215,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:11.286Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-ada0bd95f6a8d1b606c5",
              "file": "dispatch.spec.js",
              "line": 22,
              "column": 3
            }
          ]
        },
        {
          "title": "Dispatch — HTML Structure",
          "file": "dispatch.spec.js",
          "line": 29,
          "column": 6,
          "specs": [
            {
              "title": "all panes present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:32.132Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-c8dadba4d4aafc5b2f57",
              "file": "dispatch.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub navigation button present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:32.231Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-e2726a9cf4782c036a25",
              "file": "dispatch.spec.js",
              "line": 47,
              "column": 3
            },
            {
              "title": "hub auth gate present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:32.248Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-5405793d42ef9b57804f",
              "file": "dispatch.spec.js",
              "line": 52,
              "column": 3
            },
            {
              "title": "safe-bottom.css linked (ADR-081)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:32.268Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-468b5b38329dc208fd40",
              "file": "dispatch.spec.js",
              "line": 58,
              "column": 3
            },
            {
              "title": "DispatchWithAutomation Phase 1 panes present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:32.279Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-9006c02fb64910d7dd96",
              "file": "dispatch.spec.js",
              "line": 62,
              "column": 3
            },
            {
              "title": "Phase 2 proof-of-delivery: ADR-072 editor + POD overlay present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:32.294Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-4dcf170d831b6d2a9389",
              "file": "dispatch.spec.js",
              "line": 69,
              "column": 3
            },
            {
              "title": "all panes present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 7,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:11.525Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-713d9f2b949293fae3a1",
              "file": "dispatch.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub navigation button present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:11.647Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-eaf7c31f791ad5c872d7",
              "file": "dispatch.spec.js",
              "line": 47,
              "column": 3
            },
            {
              "title": "hub auth gate present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:11.671Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-779d266fdd2a744a1b06",
              "file": "dispatch.spec.js",
              "line": 52,
              "column": 3
            },
            {
              "title": "safe-bottom.css linked (ADR-081)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:11.688Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-fc15903de9591363653a",
              "file": "dispatch.spec.js",
              "line": 58,
              "column": 3
            },
            {
              "title": "DispatchWithAutomation Phase 1 panes present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:11.706Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-207cfbc606698928d018",
              "file": "dispatch.spec.js",
              "line": 62,
              "column": 3
            },
            {
              "title": "Phase 2 proof-of-delivery: ADR-072 editor + POD overlay present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:11.722Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-c772d0c5dc3f8e5cb44a",
              "file": "dispatch.spec.js",
              "line": 69,
              "column": 3
            }
          ]
        },
        {
          "title": "Dispatch — Phase 2 proof-of-delivery (mock-independent UI)",
          "file": "dispatch.spec.js",
          "line": 78,
          "column": 6,
          "specs": [
            {
              "title": "marking delivered-with-proof chains into a mandatory POD prompt",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3326,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:32.309Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-51685f4f7c89ef6f1ebe",
              "file": "dispatch.spec.js",
              "line": 79,
              "column": 3
            },
            {
              "title": "marking delivered-with-proof chains into a mandatory POD prompt",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3373,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:11.738Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-6ff4c4486bd8f411c4f9",
              "file": "dispatch.spec.js",
              "line": 79,
              "column": 3
            }
          ]
        },
        {
          "title": "Dispatch — Phase 1 non-proxy (DispatchWithAutomation)",
          "file": "dispatch.spec.js",
          "line": 106,
          "column": 6,
          "specs": [
            {
              "title": "client report page (ADR-123) loads unauthenticated",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 384,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:35.650Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-314d4b53d7b0ff78cd50",
              "file": "dispatch.spec.js",
              "line": 107,
              "column": 3
            },
            {
              "title": "campaigns tab reachable and renders the create form",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2674,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:36.050Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-11be4b1ed283479f803e",
              "file": "dispatch.spec.js",
              "line": 115,
              "column": 3
            },
            {
              "title": "client report page (ADR-123) loads unauthenticated",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 338,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:15.132Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-606bf3a949c096101f47",
              "file": "dispatch.spec.js",
              "line": 107,
              "column": 3
            },
            {
              "title": "campaigns tab reachable and renders the create form",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2575,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:15.498Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-22a35658147e93652411",
              "file": "dispatch.spec.js",
              "line": 115,
              "column": 3
            }
          ]
        },
        {
          "title": "Dispatch — Campaign create/close/list live roundtrip",
          "file": "dispatch.spec.js",
          "line": 127,
          "column": 6,
          "specs": [
            {
              "title": "create campaign via UI persists to DB and appears in list",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2770,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:38.749Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-b705a70fdb60106214c3",
              "file": "dispatch.spec.js",
              "line": 128,
              "column": 3
            },
            {
              "title": "create campaign via UI persists to DB and appears in list",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2708,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:18.085Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-a3faa01094da34a3b40b",
              "file": "dispatch.spec.js",
              "line": 128,
              "column": 3
            }
          ]
        },
        {
          "title": "Dispatch — shiprocket-proxy (ADR-117, mock or live reads)",
          "file": "dispatch.spec.js",
          "line": 148,
          "column": 6,
          "specs": [
            {
              "title": "health check reports current mode, no auth required",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 220,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 187,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:151:28",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 28,
                          "line": 151
                        },
                        "snippet": "  149 |   test('health check reports current mode, no auth required', async ({ page }) => {\n  150 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy');\n> 151 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  152 |     const body = await resp.json();\n  153 |     expect(body.status).toBe('ok');\n  154 |     expect(['mock', 'live']).toContain(body.mode);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 28,
                            "line": 151
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  149 |   test('health check reports current mode, no auth required', async ({ page }) => {\n  150 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy');\n> 151 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  152 |     const body = await resp.json();\n  153 |     expect(body.status).toBe('ok');\n  154 |     expect(['mock', 'live']).toContain(body.mode);\n    at /var/www/360lm/tests/dispatch.spec.js:151:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:41.546Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-dc213-rrent-mode-no-auth-required-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-dc213-rrent-mode-no-auth-required-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 28,
                        "line": 151
                      }
                    },
                    {
                      "workerIndex": 221,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 335,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:151:28",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 28,
                          "line": 151
                        },
                        "snippet": "  149 |   test('health check reports current mode, no auth required', async ({ page }) => {\n  150 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy');\n> 151 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  152 |     const body = await resp.json();\n  153 |     expect(body.status).toBe('ok');\n  154 |     expect(['mock', 'live']).toContain(body.mode);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 28,
                            "line": 151
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  149 |   test('health check reports current mode, no auth required', async ({ page }) => {\n  150 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy');\n> 151 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  152 |     const body = await resp.json();\n  153 |     expect(body.status).toBe('ok');\n  154 |     expect(['mock', 'live']).toContain(body.mode);\n    at /var/www/360lm/tests/dispatch.spec.js:151:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:22:42.637Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-dc213-rrent-mode-no-auth-required-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-dc213-rrent-mode-no-auth-required-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 28,
                        "line": 151
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-5fb70806081ec479fe89",
              "file": "dispatch.spec.js",
              "line": 149,
              "column": 3
            },
            {
              "title": "booking kill-switch: live creds do not imply live booking (2026-07-28 YEMO rollout)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 222,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 290,
                      "error": {
                        "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON",
                        "stack": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n    at /var/www/360lm/tests/dispatch.spec.js:161:20",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 20,
                          "line": 161
                        },
                        "snippet": "  159 |     // This is the load-bearing safety rail for the real, funded YEMO account — verified\n  160 |     // with a VALID JWT so the test proves the kill-switch itself refuses, not auth.\n> 161 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  162 |     if (health.mode !== 'live') test.skip(true, 'kill-switch only meaningful once live creds are wired');\n  163 |     expect(health.live_booking_enabled).toBe(false);\n  164 |     const resp = await page.request.post('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy/booking-points/shiprocket-yemo/book', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 20,
                            "line": 161
                          },
                          "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n\n  159 |     // This is the load-bearing safety rail for the real, funded YEMO account — verified\n  160 |     // with a VALID JWT so the test proves the kill-switch itself refuses, not auth.\n> 161 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  162 |     if (health.mode !== 'live') test.skip(true, 'kill-switch only meaningful once live creds are wired');\n  163 |     expect(health.live_booking_enabled).toBe(false);\n  164 |     const resp = await page.request.post('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy/booking-points/shiprocket-yemo/book', {\n    at /var/www/360lm/tests/dispatch.spec.js:161:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:44.049Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-c3cc2-ng-2026-07-28-YEMO-rollout--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-c3cc2-ng-2026-07-28-YEMO-rollout--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 20,
                        "line": 161
                      }
                    },
                    {
                      "workerIndex": 223,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 284,
                      "error": {
                        "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON",
                        "stack": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n    at /var/www/360lm/tests/dispatch.spec.js:161:20",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 20,
                          "line": 161
                        },
                        "snippet": "  159 |     // This is the load-bearing safety rail for the real, funded YEMO account — verified\n  160 |     // with a VALID JWT so the test proves the kill-switch itself refuses, not auth.\n> 161 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  162 |     if (health.mode !== 'live') test.skip(true, 'kill-switch only meaningful once live creds are wired');\n  163 |     expect(health.live_booking_enabled).toBe(false);\n  164 |     const resp = await page.request.post('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy/booking-points/shiprocket-yemo/book', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 20,
                            "line": 161
                          },
                          "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n\n  159 |     // This is the load-bearing safety rail for the real, funded YEMO account — verified\n  160 |     // with a VALID JWT so the test proves the kill-switch itself refuses, not auth.\n> 161 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  162 |     if (health.mode !== 'live') test.skip(true, 'kill-switch only meaningful once live creds are wired');\n  163 |     expect(health.live_booking_enabled).toBe(false);\n  164 |     const resp = await page.request.post('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy/booking-points/shiprocket-yemo/book', {\n    at /var/www/360lm/tests/dispatch.spec.js:161:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:22:45.431Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-c3cc2-ng-2026-07-28-YEMO-rollout--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-c3cc2-ng-2026-07-28-YEMO-rollout--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 20,
                        "line": 161
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-32e1a9ad953490e5f55d",
              "file": "dispatch.spec.js",
              "line": 157,
              "column": 3
            },
            {
              "title": "booking-point call without a Bearer token fails closed (ADR-105)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 224,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 297,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:177:27",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 27,
                          "line": 177
                        },
                        "snippet": "  175 |       data: { delivery_pincode: '400001' },\n  176 |     });\n> 177 |     expect(resp.status()).toBe(401);\n      |                           ^\n  178 |   });\n  179 |\n  180 |   test('select-courier (ADR-119) picks a serviceable courier with a valid JWT', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 27,
                            "line": 177
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  175 |       data: { delivery_pincode: '400001' },\n  176 |     });\n> 177 |     expect(resp.status()).toBe(401);\n      |                           ^\n  178 |   });\n  179 |\n  180 |   test('select-courier (ADR-119) picks a serviceable courier with a valid JWT', async ({ page }) => {\n    at /var/www/360lm/tests/dispatch.spec.js:177:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:46.789Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-a5264-token-fails-closed-ADR-105--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-a5264-token-fails-closed-ADR-105--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 27,
                        "line": 177
                      }
                    },
                    {
                      "workerIndex": 225,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 279,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:177:27",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 27,
                          "line": 177
                        },
                        "snippet": "  175 |       data: { delivery_pincode: '400001' },\n  176 |     });\n> 177 |     expect(resp.status()).toBe(401);\n      |                           ^\n  178 |   });\n  179 |\n  180 |   test('select-courier (ADR-119) picks a serviceable courier with a valid JWT', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 27,
                            "line": 177
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  175 |       data: { delivery_pincode: '400001' },\n  176 |     });\n> 177 |     expect(resp.status()).toBe(401);\n      |                           ^\n  178 |   });\n  179 |\n  180 |   test('select-courier (ADR-119) picks a serviceable courier with a valid JWT', async ({ page }) => {\n    at /var/www/360lm/tests/dispatch.spec.js:177:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:22:48.207Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-a5264-token-fails-closed-ADR-105--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-a5264-token-fails-closed-ADR-105--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 27,
                        "line": 177
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-a4ffa6efc99d64da3701",
              "file": "dispatch.spec.js",
              "line": 173,
              "column": 3
            },
            {
              "title": "select-courier (ADR-119) picks a serviceable courier with a valid JWT",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 226,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 248,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:186:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:186:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:49.520Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6a814-le-courier-with-a-valid-JWT-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6a814-le-courier-with-a-valid-JWT-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 227,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 231,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:186:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:186:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:22:50.807Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6a814-le-courier-with-a-valid-JWT-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6a814-le-courier-with-a-valid-JWT-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-b96975d755bd882905bf",
              "file": "dispatch.spec.js",
              "line": 180,
              "column": 3
            },
            {
              "title": "ndr worklist returns labeled mock data when mock, or an honest not-yet-implemented flag when live (ADR-118)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 228,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 245,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:199:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:199:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:52.145Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-27e43-ted-flag-when-live-ADR-118--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-27e43-ted-flag-when-live-ADR-118--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 229,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 232,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:199:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:199:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:22:53.485Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-27e43-ted-flag-when-live-ADR-118--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-27e43-ted-flag-when-live-ADR-118--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-e295208eba080dd46e4c",
              "file": "dispatch.spec.js",
              "line": 197,
              "column": 3
            },
            {
              "title": "wallet-balance endpoint returns a numeric balance (mock or live)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 230,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 246,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:214:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:214:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:54.691Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-f3f36-meric-balance-mock-or-live--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-f3f36-meric-balance-mock-or-live--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 231,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 251,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:214:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:214:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:22:56.019Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-f3f36-meric-balance-mock-or-live--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-f3f36-meric-balance-mock-or-live--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-425769e46144bef3fdd2",
              "file": "dispatch.spec.js",
              "line": 212,
              "column": 3
            },
            {
              "title": "proof photo upload -> sign -> view roundtrip, tampered/expired rejected",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 232,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 219,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:223:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:223:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:57.276Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6d738-p-tampered-expired-rejected-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6d738-p-tampered-expired-rejected-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 233,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 221,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:223:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:223:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:22:58.450Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6d738-p-tampered-expired-rejected-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6d738-p-tampered-expired-rejected-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-daf4f7b78560269173c0",
              "file": "dispatch.spec.js",
              "line": 222,
              "column": 3
            },
            {
              "title": "health check reports current mode, no auth required",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1227,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 234,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:151:28",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 28,
                          "line": 151
                        },
                        "snippet": "  149 |   test('health check reports current mode, no auth required', async ({ page }) => {\n  150 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy');\n> 151 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  152 |     const body = await resp.json();\n  153 |     expect(body.status).toBe('ok');\n  154 |     expect(['mock', 'live']).toContain(body.mode);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 28,
                            "line": 151
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  149 |   test('health check reports current mode, no auth required', async ({ page }) => {\n  150 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy');\n> 151 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  152 |     const body = await resp.json();\n  153 |     expect(body.status).toBe('ok');\n  154 |     expect(['mock', 'live']).toContain(body.mode);\n    at /var/www/360lm/tests/dispatch.spec.js:151:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:20.807Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-dc213-rrent-mode-no-auth-required-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-dc213-rrent-mode-no-auth-required-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 28,
                        "line": 151
                      }
                    },
                    {
                      "workerIndex": 1228,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 328,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:151:28",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 28,
                          "line": 151
                        },
                        "snippet": "  149 |   test('health check reports current mode, no auth required', async ({ page }) => {\n  150 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy');\n> 151 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  152 |     const body = await resp.json();\n  153 |     expect(body.status).toBe('ok');\n  154 |     expect(['mock', 'live']).toContain(body.mode);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 28,
                            "line": 151
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  149 |   test('health check reports current mode, no auth required', async ({ page }) => {\n  150 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy');\n> 151 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  152 |     const body = await resp.json();\n  153 |     expect(body.status).toBe('ok');\n  154 |     expect(['mock', 'live']).toContain(body.mode);\n    at /var/www/360lm/tests/dispatch.spec.js:151:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:22:22.051Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-dc213-rrent-mode-no-auth-required-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-dc213-rrent-mode-no-auth-required-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 28,
                        "line": 151
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-4d6f27cf6ba6c4a7069b",
              "file": "dispatch.spec.js",
              "line": 149,
              "column": 3
            },
            {
              "title": "booking kill-switch: live creds do not imply live booking (2026-07-28 YEMO rollout)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1229,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 299,
                      "error": {
                        "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON",
                        "stack": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n    at /var/www/360lm/tests/dispatch.spec.js:161:20",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 20,
                          "line": 161
                        },
                        "snippet": "  159 |     // This is the load-bearing safety rail for the real, funded YEMO account — verified\n  160 |     // with a VALID JWT so the test proves the kill-switch itself refuses, not auth.\n> 161 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  162 |     if (health.mode !== 'live') test.skip(true, 'kill-switch only meaningful once live creds are wired');\n  163 |     expect(health.live_booking_enabled).toBe(false);\n  164 |     const resp = await page.request.post('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy/booking-points/shiprocket-yemo/book', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 20,
                            "line": 161
                          },
                          "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n\n  159 |     // This is the load-bearing safety rail for the real, funded YEMO account — verified\n  160 |     // with a VALID JWT so the test proves the kill-switch itself refuses, not auth.\n> 161 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  162 |     if (health.mode !== 'live') test.skip(true, 'kill-switch only meaningful once live creds are wired');\n  163 |     expect(health.live_booking_enabled).toBe(false);\n  164 |     const resp = await page.request.post('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy/booking-points/shiprocket-yemo/book', {\n    at /var/www/360lm/tests/dispatch.spec.js:161:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:23.450Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-c3cc2-ng-2026-07-28-YEMO-rollout--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-c3cc2-ng-2026-07-28-YEMO-rollout--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 20,
                        "line": 161
                      }
                    },
                    {
                      "workerIndex": 1230,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 272,
                      "error": {
                        "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON",
                        "stack": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n    at /var/www/360lm/tests/dispatch.spec.js:161:20",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 20,
                          "line": 161
                        },
                        "snippet": "  159 |     // This is the load-bearing safety rail for the real, funded YEMO account — verified\n  160 |     // with a VALID JWT so the test proves the kill-switch itself refuses, not auth.\n> 161 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  162 |     if (health.mode !== 'live') test.skip(true, 'kill-switch only meaningful once live creds are wired');\n  163 |     expect(health.live_booking_enabled).toBe(false);\n  164 |     const resp = await page.request.post('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy/booking-points/shiprocket-yemo/book', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 20,
                            "line": 161
                          },
                          "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n\n  159 |     // This is the load-bearing safety rail for the real, funded YEMO account — verified\n  160 |     // with a VALID JWT so the test proves the kill-switch itself refuses, not auth.\n> 161 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  162 |     if (health.mode !== 'live') test.skip(true, 'kill-switch only meaningful once live creds are wired');\n  163 |     expect(health.live_booking_enabled).toBe(false);\n  164 |     const resp = await page.request.post('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy/booking-points/shiprocket-yemo/book', {\n    at /var/www/360lm/tests/dispatch.spec.js:161:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:22:24.744Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-c3cc2-ng-2026-07-28-YEMO-rollout--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-c3cc2-ng-2026-07-28-YEMO-rollout--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 20,
                        "line": 161
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-fdb2c04a724355ad3da0",
              "file": "dispatch.spec.js",
              "line": 157,
              "column": 3
            },
            {
              "title": "booking-point call without a Bearer token fails closed (ADR-105)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1231,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 294,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:177:27",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 27,
                          "line": 177
                        },
                        "snippet": "  175 |       data: { delivery_pincode: '400001' },\n  176 |     });\n> 177 |     expect(resp.status()).toBe(401);\n      |                           ^\n  178 |   });\n  179 |\n  180 |   test('select-courier (ADR-119) picks a serviceable courier with a valid JWT', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 27,
                            "line": 177
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  175 |       data: { delivery_pincode: '400001' },\n  176 |     });\n> 177 |     expect(resp.status()).toBe(401);\n      |                           ^\n  178 |   });\n  179 |\n  180 |   test('select-courier (ADR-119) picks a serviceable courier with a valid JWT', async ({ page }) => {\n    at /var/www/360lm/tests/dispatch.spec.js:177:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:26.082Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-a5264-token-fails-closed-ADR-105--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-a5264-token-fails-closed-ADR-105--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 27,
                        "line": 177
                      }
                    },
                    {
                      "workerIndex": 1232,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 278,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:177:27",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 27,
                          "line": 177
                        },
                        "snippet": "  175 |       data: { delivery_pincode: '400001' },\n  176 |     });\n> 177 |     expect(resp.status()).toBe(401);\n      |                           ^\n  178 |   });\n  179 |\n  180 |   test('select-courier (ADR-119) picks a serviceable courier with a valid JWT', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 27,
                            "line": 177
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m401\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  175 |       data: { delivery_pincode: '400001' },\n  176 |     });\n> 177 |     expect(resp.status()).toBe(401);\n      |                           ^\n  178 |   });\n  179 |\n  180 |   test('select-courier (ADR-119) picks a serviceable courier with a valid JWT', async ({ page }) => {\n    at /var/www/360lm/tests/dispatch.spec.js:177:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:22:27.439Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-a5264-token-fails-closed-ADR-105--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-a5264-token-fails-closed-ADR-105--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 27,
                        "line": 177
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-b75c4c9a148ce08d06b4",
              "file": "dispatch.spec.js",
              "line": 173,
              "column": 3
            },
            {
              "title": "select-courier (ADR-119) picks a serviceable courier with a valid JWT",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1233,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 258,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:186:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:186:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:28.723Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6a814-le-courier-with-a-valid-JWT-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6a814-le-courier-with-a-valid-JWT-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 1234,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 248,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:186:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:186:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:22:30.006Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6a814-le-courier-with-a-valid-JWT-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6a814-le-courier-with-a-valid-JWT-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-6173888ab76b2fa0dc61",
              "file": "dispatch.spec.js",
              "line": 180,
              "column": 3
            },
            {
              "title": "ndr worklist returns labeled mock data when mock, or an honest not-yet-implemented flag when live (ADR-118)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1235,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 239,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:199:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:199:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:31.393Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-27e43-ted-flag-when-live-ADR-118--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-27e43-ted-flag-when-live-ADR-118--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 1236,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 261,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:199:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:199:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:22:32.839Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-27e43-ted-flag-when-live-ADR-118--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-27e43-ted-flag-when-live-ADR-118--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-83ed1acb813a361c73b1",
              "file": "dispatch.spec.js",
              "line": 197,
              "column": 3
            },
            {
              "title": "wallet-balance endpoint returns a numeric balance (mock or live)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1237,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 235,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:214:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:214:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:34.262Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-f3f36-meric-balance-mock-or-live--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-f3f36-meric-balance-mock-or-live--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 1238,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 274,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:214:43",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:214:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:22:35.567Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-f3f36-meric-balance-mock-or-live--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-f3f36-meric-balance-mock-or-live--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-f5fc34b80e1a3a7574e2",
              "file": "dispatch.spec.js",
              "line": 212,
              "column": 3
            },
            {
              "title": "proof photo upload -> sign -> view roundtrip, tampered/expired rejected",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1239,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 269,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:223:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:223:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:37.027Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6d738-p-tampered-expired-rejected-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6d738-p-tampered-expired-rejected-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 1240,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 246,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:223:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:223:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:22:38.408Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6d738-p-tampered-expired-rejected-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-shipro-6d738-p-tampered-expired-rejected-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-2a5131d06a85a824613d",
              "file": "dispatch.spec.js",
              "line": 222,
              "column": 3
            }
          ]
        },
        {
          "title": "Dispatch — Xpressbees manual worklist (ADR-118)",
          "file": "dispatch.spec.js",
          "line": 266,
          "column": 6,
          "specs": [
            {
              "title": "worklist reachable from TRACK pane",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 234,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2937,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:22:59.650Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-a1370ad239f65355e3c9",
              "file": "dispatch.spec.js",
              "line": 267,
              "column": 3
            },
            {
              "title": "worklist reachable from TRACK pane",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1241,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2922,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:39.820Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-11bf996df275e7538672",
              "file": "dispatch.spec.js",
              "line": 267,
              "column": 3
            }
          ]
        },
        {
          "title": "Dispatch — DTDC manual worklist (ADR-118 amended — relay retired)",
          "file": "dispatch.spec.js",
          "line": 277,
          "column": 6,
          "specs": [
            {
              "title": "worklist reachable from TRACK pane",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 234,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2871,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:02.790Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-941f0226e4bba7293df4",
              "file": "dispatch.spec.js",
              "line": 278,
              "column": 3
            },
            {
              "title": "dtdc-relay-proxy endpoints are torn down (not orphaned)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 234,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 160,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:05.685Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-be00e438cb15ebe6e649",
              "file": "dispatch.spec.js",
              "line": 287,
              "column": 3
            },
            {
              "title": "worklist reachable from TRACK pane",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1241,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2607,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:42.918Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-faf87502c850e3a668e9",
              "file": "dispatch.spec.js",
              "line": 278,
              "column": 3
            },
            {
              "title": "dtdc-relay-proxy endpoints are torn down (not orphaned)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1241,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 176,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:45.545Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-f52755f7efffbbd17453",
              "file": "dispatch.spec.js",
              "line": 287,
              "column": 3
            }
          ]
        },
        {
          "title": "Dispatch — offline proof-photo draft queue (Q0.3/Q0.10)",
          "file": "dispatch.spec.js",
          "line": 295,
          "column": 6,
          "specs": [
            {
              "title": "IndexedDB queue save/list/delete round-trips",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 234,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 673,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:05.868Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-9c9c389e638aacd03993",
              "file": "dispatch.spec.js",
              "line": 296,
              "column": 3
            },
            {
              "title": "IndexedDB queue save/list/delete round-trips",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1241,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 612,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:45.739Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-ddc669d30eb2ddc9f6b9",
              "file": "dispatch.spec.js",
              "line": 296,
              "column": 3
            }
          ]
        },
        {
          "title": "Dispatch — Phase 3 prep (ADR-122 booking-point adapters, ADR-119 cheapest-within-SLA, carrier scorecard)",
          "file": "dispatch.spec.js",
          "line": 319,
          "column": 6,
          "specs": [
            {
              "title": "new mock booking points are reachable through the adapter interface",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 234,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 153,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:321:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:321:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:06.560Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--80efa-rough-the-adapter-interface-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--80efa-rough-the-adapter-interface-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 235,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 260,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:321:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:321:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:23:07.666Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--80efa-rough-the-adapter-interface-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--80efa-rough-the-adapter-interface-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-6b5f55f44a7bd8e3004d",
              "file": "dispatch.spec.js",
              "line": 320,
              "column": 3
            },
            {
              "title": "cheapest_within_sla rule picks the cheapest courier meeting the SLA, not the fastest",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 236,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 268,
                      "error": {
                        "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON",
                        "stack": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n    at /var/www/360lm/tests/dispatch.spec.js:337:20",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 20,
                          "line": 337
                        },
                        "snippet": "  335 |\n  336 |   test('cheapest_within_sla rule picks the cheapest courier meeting the SLA, not the fastest', async ({ page }) => {\n> 337 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  338 |     if (health.mode !== 'mock') test.skip(true, 'asserts against fixed mock-rate values (Blue Dart/Delhivery/Ecom Express @ ₹145/118/95) — meaningless once rates are live and variable');\n  339 |     const token = mintTestJwt();\n  340 |     const ruleId = 'PWTEST-SLA-' + Date.now();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 20,
                            "line": 337
                          },
                          "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n\n  335 |\n  336 |   test('cheapest_within_sla rule picks the cheapest courier meeting the SLA, not the fastest', async ({ page }) => {\n> 337 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  338 |     if (health.mode !== 'mock') test.skip(true, 'asserts against fixed mock-rate values (Blue Dart/Delhivery/Ecom Express @ ₹145/118/95) — meaningless once rates are live and variable');\n  339 |     const token = mintTestJwt();\n  340 |     const ruleId = 'PWTEST-SLA-' + Date.now();\n    at /var/www/360lm/tests/dispatch.spec.js:337:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:08.945Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--e1e3b-ing-the-SLA-not-the-fastest-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--e1e3b-ing-the-SLA-not-the-fastest-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 20,
                        "line": 337
                      }
                    },
                    {
                      "workerIndex": 237,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 318,
                      "error": {
                        "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON",
                        "stack": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n    at /var/www/360lm/tests/dispatch.spec.js:337:20",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 20,
                          "line": 337
                        },
                        "snippet": "  335 |\n  336 |   test('cheapest_within_sla rule picks the cheapest courier meeting the SLA, not the fastest', async ({ page }) => {\n> 337 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  338 |     if (health.mode !== 'mock') test.skip(true, 'asserts against fixed mock-rate values (Blue Dart/Delhivery/Ecom Express @ ₹145/118/95) — meaningless once rates are live and variable');\n  339 |     const token = mintTestJwt();\n  340 |     const ruleId = 'PWTEST-SLA-' + Date.now();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 20,
                            "line": 337
                          },
                          "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n\n  335 |\n  336 |   test('cheapest_within_sla rule picks the cheapest courier meeting the SLA, not the fastest', async ({ page }) => {\n> 337 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  338 |     if (health.mode !== 'mock') test.skip(true, 'asserts against fixed mock-rate values (Blue Dart/Delhivery/Ecom Express @ ₹145/118/95) — meaningless once rates are live and variable');\n  339 |     const token = mintTestJwt();\n  340 |     const ruleId = 'PWTEST-SLA-' + Date.now();\n    at /var/www/360lm/tests/dispatch.spec.js:337:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:23:10.139Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--e1e3b-ing-the-SLA-not-the-fastest-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--e1e3b-ing-the-SLA-not-the-fastest-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 20,
                        "line": 337
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-09b96e7bef58ba36257a",
              "file": "dispatch.spec.js",
              "line": 336,
              "column": 3
            },
            {
              "title": "carrier scorecard RPC reachable via web_anon (empty in dev — no real bookings yet)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 238,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 232,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:11.497Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-5ff8975315d3e935902f",
              "file": "dispatch.spec.js",
              "line": 357,
              "column": 3
            },
            {
              "title": "scorecard + booking-rules SLA field visible in CONFIG (admin)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 238,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2741,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:11.891Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-b51bfb2742fd3b67732c",
              "file": "dispatch.spec.js",
              "line": 365,
              "column": 3
            },
            {
              "title": "new mock booking points are reachable through the adapter interface",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1241,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 165,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:321:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:321:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:46.369Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--80efa-rough-the-adapter-interface-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--80efa-rough-the-adapter-interface-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 1242,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 269,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:321:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:321:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:22:47.505Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--80efa-rough-the-adapter-interface-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--80efa-rough-the-adapter-interface-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-d50273eb5a9eb396ae3d",
              "file": "dispatch.spec.js",
              "line": 320,
              "column": 3
            },
            {
              "title": "cheapest_within_sla rule picks the cheapest courier meeting the SLA, not the fastest",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1243,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 283,
                      "error": {
                        "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON",
                        "stack": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n    at /var/www/360lm/tests/dispatch.spec.js:337:20",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 20,
                          "line": 337
                        },
                        "snippet": "  335 |\n  336 |   test('cheapest_within_sla rule picks the cheapest courier meeting the SLA, not the fastest', async ({ page }) => {\n> 337 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  338 |     if (health.mode !== 'mock') test.skip(true, 'asserts against fixed mock-rate values (Blue Dart/Delhivery/Ecom Express @ ₹145/118/95) — meaningless once rates are live and variable');\n  339 |     const token = mintTestJwt();\n  340 |     const ruleId = 'PWTEST-SLA-' + Date.now();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 20,
                            "line": 337
                          },
                          "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n\n  335 |\n  336 |   test('cheapest_within_sla rule picks the cheapest courier meeting the SLA, not the fastest', async ({ page }) => {\n> 337 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  338 |     if (health.mode !== 'mock') test.skip(true, 'asserts against fixed mock-rate values (Blue Dart/Delhivery/Ecom Express @ ₹145/118/95) — meaningless once rates are live and variable');\n  339 |     const token = mintTestJwt();\n  340 |     const ruleId = 'PWTEST-SLA-' + Date.now();\n    at /var/www/360lm/tests/dispatch.spec.js:337:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:48.944Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--e1e3b-ing-the-SLA-not-the-fastest-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--e1e3b-ing-the-SLA-not-the-fastest-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 20,
                        "line": 337
                      }
                    },
                    {
                      "workerIndex": 1244,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 301,
                      "error": {
                        "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON",
                        "stack": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n    at /var/www/360lm/tests/dispatch.spec.js:337:20",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 20,
                          "line": 337
                        },
                        "snippet": "  335 |\n  336 |   test('cheapest_within_sla rule picks the cheapest courier meeting the SLA, not the fastest', async ({ page }) => {\n> 337 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  338 |     if (health.mode !== 'mock') test.skip(true, 'asserts against fixed mock-rate values (Blue Dart/Delhivery/Ecom Express @ ₹145/118/95) — meaningless once rates are live and variable');\n  339 |     const token = mintTestJwt();\n  340 |     const ruleId = 'PWTEST-SLA-' + Date.now();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 20,
                            "line": 337
                          },
                          "message": "SyntaxError: Unexpected token '<', \"<!DOCTYPE \"... is not valid JSON\n\n  335 |\n  336 |   test('cheapest_within_sla rule picks the cheapest courier meeting the SLA, not the fastest', async ({ page }) => {\n> 337 |     const health = await (await page.request.get('https://dev.srv1111289.hstgr.cloud/shiprocket-proxy')).json();\n      |                    ^\n  338 |     if (health.mode !== 'mock') test.skip(true, 'asserts against fixed mock-rate values (Blue Dart/Delhivery/Ecom Express @ ₹145/118/95) — meaningless once rates are live and variable');\n  339 |     const token = mintTestJwt();\n  340 |     const ruleId = 'PWTEST-SLA-' + Date.now();\n    at /var/www/360lm/tests/dispatch.spec.js:337:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:22:50.293Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--e1e3b-ing-the-SLA-not-the-fastest-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-Phase--e1e3b-ing-the-SLA-not-the-fastest-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 20,
                        "line": 337
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-a43c9b87e8fae39ce792",
              "file": "dispatch.spec.js",
              "line": 336,
              "column": 3
            },
            {
              "title": "carrier scorecard RPC reachable via web_anon (empty in dev — no real bookings yet)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1245,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 289,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:51.688Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-e6ef7bcb33e548153916",
              "file": "dispatch.spec.js",
              "line": 357,
              "column": 3
            },
            {
              "title": "scorecard + booking-rules SLA field visible in CONFIG (admin)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1245,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2812,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:52.186Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-c2bf7cddb33aedae52f8",
              "file": "dispatch.spec.js",
              "line": 365,
              "column": 3
            }
          ]
        },
        {
          "title": "Dispatch — ADR-118 reconcile-correctness audit (2026-07-27, TIMEX-informed)",
          "file": "dispatch.spec.js",
          "line": 378,
          "column": 6,
          "specs": [
            {
              "title": "\"Delivered\" is unconfirmed until an independent (non-Shiprocket) source agrees",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 238,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 208,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:14.658Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-0ac306e203e127cbd49a",
              "file": "dispatch.spec.js",
              "line": 379,
              "column": 3
            },
            {
              "title": "mock /track tags events with the correct source per booking-point (not \"manual\" for every mock result)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 238,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 162,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:417:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:417:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:14.900Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-ADR-11-b174e-nual-for-every-mock-result--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-ADR-11-b174e-nual-for-every-mock-result--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 239,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 225,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:417:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:417:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:23:15.933Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-ADR-11-b174e-nual-for-every-mock-result--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-ADR-11-b174e-nual-for-every-mock-result--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-78146b15195d558627b7",
              "file": "dispatch.spec.js",
              "line": 416,
              "column": 3
            },
            {
              "title": "\"Delivered\" is unconfirmed until an independent (non-Shiprocket) source agrees",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1245,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 211,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:55.050Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "3510226c3ce1e611dacb-ddf6c4f1a9b3d29aa218",
              "file": "dispatch.spec.js",
              "line": 379,
              "column": 3
            },
            {
              "title": "mock /track tags events with the correct source per booking-point (not \"manual\" for every mock result)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1245,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 151,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:417:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:417:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:55.285Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-ADR-11-b174e-nual-for-every-mock-result--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-ADR-11-b174e-nual-for-every-mock-result--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    },
                    {
                      "workerIndex": 1246,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 286,
                      "error": {
                        "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests",
                        "stack": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:417:19",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 22,
                          "line": 259
                        },
                        "snippet": "  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 22,
                            "line": 259
                          },
                          "message": "Error: HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests\n\n  257 |   // running this spec file, e.g.: `set -a; source /root/360lm-web/.env; set +a; npx playwright test ...`\n  258 |   const secret = process.env.HUB_JWT_SECRET;\n> 259 |   if (!secret) throw new Error('HUB_JWT_SECRET not set in environment — source /root/360lm-web/.env before running these tests');\n      |                      ^\n  260 |   const header = b64url({ alg: 'HS256', typ: 'JWT' });\n  261 |   const payload = b64url({ sub: 'pw-test', exp: Math.floor(Date.now() / 1000) + 1800 });\n  262 |   const sig = crypto.createHmac('sha256', secret).update(`${header}.${payload}`).digest('base64url');\n    at mintTestJwt (/var/www/360lm/tests/dispatch.spec.js:259:22)\n    at /var/www/360lm/tests/dispatch.spec.js:417:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:22:56.423Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-ADR-11-b174e-nual-for-every-mock-result--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-ADR-11-b174e-nual-for-every-mock-result--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 22,
                        "line": 259
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-9491b2f88c93e4ae0387",
              "file": "dispatch.spec.js",
              "line": 416,
              "column": 3
            }
          ]
        },
        {
          "title": "Dispatch — dispatch-ai ADR-062 tiered address parse (2026-07-28 fix)",
          "file": "dispatch.spec.js",
          "line": 440,
          "column": 6,
          "specs": [
            {
              "title": "health reports the full tiered chain, not Ollama-only",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 240,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 263,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:443:28",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 28,
                          "line": 443
                        },
                        "snippet": "  441 |   test('health reports the full tiered chain, not Ollama-only', async ({ page }) => {\n  442 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/dispatch-ai');\n> 443 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  444 |     const body = await resp.json();\n  445 |     expect(body.address_tiers).toEqual(['claude_oauth', 'openrouter', 'anthropic_api', 'ollama']);\n  446 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 28,
                            "line": 443
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  441 |   test('health reports the full tiered chain, not Ollama-only', async ({ page }) => {\n  442 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/dispatch-ai');\n> 443 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  444 |     const body = await resp.json();\n  445 |     expect(body.address_tiers).toEqual(['claude_oauth', 'openrouter', 'anthropic_api', 'ollama']);\n  446 |   });\n    at /var/www/360lm/tests/dispatch.spec.js:443:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:17.179Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-ffff5-iered-chain-not-Ollama-only-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-ffff5-iered-chain-not-Ollama-only-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 28,
                        "line": 443
                      }
                    },
                    {
                      "workerIndex": 241,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 285,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:443:28",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 28,
                          "line": 443
                        },
                        "snippet": "  441 |   test('health reports the full tiered chain, not Ollama-only', async ({ page }) => {\n  442 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/dispatch-ai');\n> 443 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  444 |     const body = await resp.json();\n  445 |     expect(body.address_tiers).toEqual(['claude_oauth', 'openrouter', 'anthropic_api', 'ollama']);\n  446 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 28,
                            "line": 443
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  441 |   test('health reports the full tiered chain, not Ollama-only', async ({ page }) => {\n  442 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/dispatch-ai');\n> 443 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  444 |     const body = await resp.json();\n  445 |     expect(body.address_tiers).toEqual(['claude_oauth', 'openrouter', 'anthropic_api', 'ollama']);\n  446 |   });\n    at /var/www/360lm/tests/dispatch.spec.js:443:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:23:18.374Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-ffff5-iered-chain-not-Ollama-only-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-ffff5-iered-chain-not-Ollama-only-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 28,
                        "line": 443
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-233ef25d8b8499aa8fbe",
              "file": "dispatch.spec.js",
              "line": 441,
              "column": 3
            },
            {
              "title": "parses a real order-booking row via claude_oauth, no timeout, all 7 fields present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 242,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 278,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:453:27",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 27,
                          "line": 453
                        },
                        "snippet": "  451 |       timeout: 30000, // was a 60s gateway timeout under the old Ollama-only path — must now resolve well inside that\n  452 |     });\n> 453 |     expect(resp.status()).toBe(200);\n      |                           ^\n  454 |     const body = await resp.json();\n  455 |     expect(body.success).toBe(true);\n  456 |     expect(body.mode).toBe('claude_oauth');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 27,
                            "line": 453
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  451 |       timeout: 30000, // was a 60s gateway timeout under the old Ollama-only path — must now resolve well inside that\n  452 |     });\n> 453 |     expect(resp.status()).toBe(200);\n      |                           ^\n  454 |     const body = await resp.json();\n  455 |     expect(body.success).toBe(true);\n  456 |     expect(body.mode).toBe('claude_oauth');\n    at /var/www/360lm/tests/dispatch.spec.js:453:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:19.709Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-1655b-imeout-all-7-fields-present-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-1655b-imeout-all-7-fields-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 27,
                        "line": 453
                      }
                    },
                    {
                      "workerIndex": 243,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 271,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:453:27",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 27,
                          "line": 453
                        },
                        "snippet": "  451 |       timeout: 30000, // was a 60s gateway timeout under the old Ollama-only path — must now resolve well inside that\n  452 |     });\n> 453 |     expect(resp.status()).toBe(200);\n      |                           ^\n  454 |     const body = await resp.json();\n  455 |     expect(body.success).toBe(true);\n  456 |     expect(body.mode).toBe('claude_oauth');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 27,
                            "line": 453
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  451 |       timeout: 30000, // was a 60s gateway timeout under the old Ollama-only path — must now resolve well inside that\n  452 |     });\n> 453 |     expect(resp.status()).toBe(200);\n      |                           ^\n  454 |     const body = await resp.json();\n  455 |     expect(body.success).toBe(true);\n  456 |     expect(body.mode).toBe('claude_oauth');\n    at /var/www/360lm/tests/dispatch.spec.js:453:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:23:20.978Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-1655b-imeout-all-7-fields-present-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-1655b-imeout-all-7-fields-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 27,
                        "line": 453
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-c0a84248644afb556c74",
              "file": "dispatch.spec.js",
              "line": 448,
              "column": 3
            },
            {
              "title": "health reports the full tiered chain, not Ollama-only",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1247,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 281,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:443:28",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 28,
                          "line": 443
                        },
                        "snippet": "  441 |   test('health reports the full tiered chain, not Ollama-only', async ({ page }) => {\n  442 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/dispatch-ai');\n> 443 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  444 |     const body = await resp.json();\n  445 |     expect(body.address_tiers).toEqual(['claude_oauth', 'openrouter', 'anthropic_api', 'ollama']);\n  446 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 28,
                            "line": 443
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  441 |   test('health reports the full tiered chain, not Ollama-only', async ({ page }) => {\n  442 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/dispatch-ai');\n> 443 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  444 |     const body = await resp.json();\n  445 |     expect(body.address_tiers).toEqual(['claude_oauth', 'openrouter', 'anthropic_api', 'ollama']);\n  446 |   });\n    at /var/www/360lm/tests/dispatch.spec.js:443:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:22:57.763Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-ffff5-iered-chain-not-Ollama-only-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-ffff5-iered-chain-not-Ollama-only-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 28,
                        "line": 443
                      }
                    },
                    {
                      "workerIndex": 1248,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 340,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:443:28",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 28,
                          "line": 443
                        },
                        "snippet": "  441 |   test('health reports the full tiered chain, not Ollama-only', async ({ page }) => {\n  442 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/dispatch-ai');\n> 443 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  444 |     const body = await resp.json();\n  445 |     expect(body.address_tiers).toEqual(['claude_oauth', 'openrouter', 'anthropic_api', 'ollama']);\n  446 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 28,
                            "line": 443
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  441 |   test('health reports the full tiered chain, not Ollama-only', async ({ page }) => {\n  442 |     const resp = await page.goto('https://dev.srv1111289.hstgr.cloud/dispatch-ai');\n> 443 |     expect(resp?.status()).toBe(200);\n      |                            ^\n  444 |     const body = await resp.json();\n  445 |     expect(body.address_tiers).toEqual(['claude_oauth', 'openrouter', 'anthropic_api', 'ollama']);\n  446 |   });\n    at /var/www/360lm/tests/dispatch.spec.js:443:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:22:59.125Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-ffff5-iered-chain-not-Ollama-only-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-ffff5-iered-chain-not-Ollama-only-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 28,
                        "line": 443
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-a368990bd986ccc898a9",
              "file": "dispatch.spec.js",
              "line": 441,
              "column": 3
            },
            {
              "title": "parses a real order-booking row via claude_oauth, no timeout, all 7 fields present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1249,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 391,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:453:27",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 27,
                          "line": 453
                        },
                        "snippet": "  451 |       timeout: 30000, // was a 60s gateway timeout under the old Ollama-only path — must now resolve well inside that\n  452 |     });\n> 453 |     expect(resp.status()).toBe(200);\n      |                           ^\n  454 |     const body = await resp.json();\n  455 |     expect(body.success).toBe(true);\n  456 |     expect(body.mode).toBe('claude_oauth');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 27,
                            "line": 453
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  451 |       timeout: 30000, // was a 60s gateway timeout under the old Ollama-only path — must now resolve well inside that\n  452 |     });\n> 453 |     expect(resp.status()).toBe(200);\n      |                           ^\n  454 |     const body = await resp.json();\n  455 |     expect(body.success).toBe(true);\n  456 |     expect(body.mode).toBe('claude_oauth');\n    at /var/www/360lm/tests/dispatch.spec.js:453:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:00.740Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-1655b-imeout-all-7-fields-present-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-1655b-imeout-all-7-fields-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 27,
                        "line": 453
                      }
                    },
                    {
                      "workerIndex": 1250,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 339,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/dispatch.spec.js:453:27",
                        "location": {
                          "file": "/var/www/360lm/tests/dispatch.spec.js",
                          "column": 27,
                          "line": 453
                        },
                        "snippet": "  451 |       timeout: 30000, // was a 60s gateway timeout under the old Ollama-only path — must now resolve well inside that\n  452 |     });\n> 453 |     expect(resp.status()).toBe(200);\n      |                           ^\n  454 |     const body = await resp.json();\n  455 |     expect(body.success).toBe(true);\n  456 |     expect(body.mode).toBe('claude_oauth');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/dispatch.spec.js",
                            "column": 27,
                            "line": 453
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  451 |       timeout: 30000, // was a 60s gateway timeout under the old Ollama-only path — must now resolve well inside that\n  452 |     });\n> 453 |     expect(resp.status()).toBe(200);\n      |                           ^\n  454 |     const body = await resp.json();\n  455 |     expect(body.success).toBe(true);\n  456 |     expect(body.mode).toBe('claude_oauth');\n    at /var/www/360lm/tests/dispatch.spec.js:453:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:23:02.708Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-1655b-imeout-all-7-fields-present-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/dispatch-Dispatch-—-dispat-1655b-imeout-all-7-fields-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/dispatch.spec.js",
                        "column": 27,
                        "line": 453
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "3510226c3ce1e611dacb-febd6e0160681564c7bf",
              "file": "dispatch.spec.js",
              "line": 448,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "drive_consolidator.spec.js",
      "file": "drive_consolidator.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Drive Consolidator — Auth / Profile",
          "file": "drive_consolidator.spec.js",
          "line": 156,
          "column": 6,
          "specs": [
            {
              "title": "first launch (no users) shows profile-create screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 641,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:22.372Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-08ea729e51c0ddf89606",
              "file": "drive_consolidator.spec.js",
              "line": 157,
              "column": 3
            },
            {
              "title": "existing users show login screen on load",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 385,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:23.201Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-429d541241df4d666b6d",
              "file": "drive_consolidator.spec.js",
              "line": 164,
              "column": 3
            },
            {
              "title": "tap user card → PIN screen appears with user name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 458,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:23.626Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-812c3413246029b205a1",
              "file": "drive_consolidator.spec.js",
              "line": 171,
              "column": 3
            },
            {
              "title": "correct PIN logs in → accounts screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 818,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:24.111Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-21faec9b340c0959f147",
              "file": "drive_consolidator.spec.js",
              "line": 179,
              "column": 3
            },
            {
              "title": "wrong PIN shows error toast, resets dots",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1628,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:24.958Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-fc66dc2c5916fe646d18",
              "file": "drive_consolidator.spec.js",
              "line": 190,
              "column": 3
            },
            {
              "title": "back link from PIN → login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 438,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:26.601Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-65f9b8397ad580569fd0",
              "file": "drive_consolidator.spec.js",
              "line": 205,
              "column": 3
            },
            {
              "title": "logout button returns to login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 877,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:27.057Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-3b6439d478d94df7174a",
              "file": "drive_consolidator.spec.js",
              "line": 214,
              "column": 3
            },
            {
              "title": "create profile: no users → profile-create, save → accounts",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1122,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:27.949Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-e3dbd7557b2817e3d460",
              "file": "drive_consolidator.spec.js",
              "line": 221,
              "column": 3
            },
            {
              "title": "mismatched confirm PIN resets and shows toast",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1187,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:29.086Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-cbab6ba54cecebb1d72f",
              "file": "drive_consolidator.spec.js",
              "line": 245,
              "column": 3
            },
            {
              "title": "first launch (no users) shows profile-create screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 704,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:04.411Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-4a5a45c16d495813ef05",
              "file": "drive_consolidator.spec.js",
              "line": 157,
              "column": 3
            },
            {
              "title": "existing users show login screen on load",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 446,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:05.358Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-48ca91c16f0c631372ad",
              "file": "drive_consolidator.spec.js",
              "line": 164,
              "column": 3
            },
            {
              "title": "tap user card → PIN screen appears with user name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 533,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:05.827Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-0cf1b9896ccdccfd0ed1",
              "file": "drive_consolidator.spec.js",
              "line": 171,
              "column": 3
            },
            {
              "title": "correct PIN logs in → accounts screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1002,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:06.394Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-6f78449d10d8cdf9dc78",
              "file": "drive_consolidator.spec.js",
              "line": 179,
              "column": 3
            },
            {
              "title": "wrong PIN shows error toast, resets dots",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1695,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:07.422Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-ffb8741d6b30c435b9c9",
              "file": "drive_consolidator.spec.js",
              "line": 190,
              "column": 3
            },
            {
              "title": "back link from PIN → login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 464,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:09.150Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-a00bcc57220eeb3ef2fd",
              "file": "drive_consolidator.spec.js",
              "line": 205,
              "column": 3
            },
            {
              "title": "logout button returns to login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 820,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:09.632Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-00e0a7cca81a13ef2230",
              "file": "drive_consolidator.spec.js",
              "line": 214,
              "column": 3
            },
            {
              "title": "create profile: no users → profile-create, save → accounts",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1138,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:10.474Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-2dd130eb93d9b3ed3612",
              "file": "drive_consolidator.spec.js",
              "line": 221,
              "column": 3
            },
            {
              "title": "mismatched confirm PIN resets and shows toast",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1280,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:11.629Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-dee18efeac12d403ffbe",
              "file": "drive_consolidator.spec.js",
              "line": 245,
              "column": 3
            }
          ]
        },
        {
          "title": "Drive Consolidator — Setup",
          "file": "drive_consolidator.spec.js",
          "line": 265,
          "column": 6,
          "specs": [
            {
              "title": "no client ID after login → shows setup screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 769,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:30.293Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-42eaebababd07e260ac0",
              "file": "drive_consolidator.spec.js",
              "line": 266,
              "column": 3
            },
            {
              "title": "save client ID → advances to Accounts screen + persists",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 819,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:31.077Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-cb1eca2825861f403038",
              "file": "drive_consolidator.spec.js",
              "line": 276,
              "column": 3
            },
            {
              "title": "empty client ID shows toast, stays on setup",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 827,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:31.913Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-c92f6b3731dcd83bf281",
              "file": "drive_consolidator.spec.js",
              "line": 291,
              "column": 3
            },
            {
              "title": "settings button opens setup with clientId pre-filled",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 785,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:32.753Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-e23912527d28ba42359f",
              "file": "drive_consolidator.spec.js",
              "line": 303,
              "column": 3
            },
            {
              "title": "client ID persists — re-login after reload goes straight to accounts",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1340,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:33.552Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-646656d59a20fbe1d213",
              "file": "drive_consolidator.spec.js",
              "line": 310,
              "column": 3
            },
            {
              "title": "no client ID after login → shows setup screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 830,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:12.928Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-a15d08a8d5aa915c48d1",
              "file": "drive_consolidator.spec.js",
              "line": 266,
              "column": 3
            },
            {
              "title": "save client ID → advances to Accounts screen + persists",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 926,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:13.777Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-0a18809750f3ca718795",
              "file": "drive_consolidator.spec.js",
              "line": 276,
              "column": 3
            },
            {
              "title": "empty client ID shows toast, stays on setup",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 862,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:14.720Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-4447d8997433aebed2db",
              "file": "drive_consolidator.spec.js",
              "line": 291,
              "column": 3
            },
            {
              "title": "settings button opens setup with clientId pre-filled",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 858,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:15.605Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-c070a6e19abab14be2fc",
              "file": "drive_consolidator.spec.js",
              "line": 303,
              "column": 3
            },
            {
              "title": "client ID persists — re-login after reload goes straight to accounts",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1396,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:16.482Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-e079dc1e560f6010db03",
              "file": "drive_consolidator.spec.js",
              "line": 310,
              "column": 3
            }
          ]
        },
        {
          "title": "Drive Consolidator — Accounts",
          "file": "drive_consolidator.spec.js",
          "line": 322,
          "column": 6,
          "specs": [
            {
              "title": "shows empty state before any account connected",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 823,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:34.907Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-1f43126ad0002e41fc90",
              "file": "drive_consolidator.spec.js",
              "line": 323,
              "column": 3
            },
            {
              "title": "step indicator shows step 1 active",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 799,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:35.752Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-767fa2450985e3b6538a",
              "file": "drive_consolidator.spec.js",
              "line": 329,
              "column": 3
            },
            {
              "title": "connect account → shows in list with name and email",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 856,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:36.566Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-d5fa992a947a48b4f9cb",
              "file": "drive_consolidator.spec.js",
              "line": 334,
              "column": 3
            },
            {
              "title": "remove account button removes it from list",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 964,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:37.439Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-0d477060dbce21ad5899",
              "file": "drive_consolidator.spec.js",
              "line": 350,
              "column": 3
            },
            {
              "title": "scan button shows toast if no accounts connected",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 877,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:38.419Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-8a7b2144e57ce3fdecab",
              "file": "drive_consolidator.spec.js",
              "line": 366,
              "column": 3
            },
            {
              "title": "shows empty state before any account connected",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 768,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:17.896Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-b97c2649bf416ff12416",
              "file": "drive_consolidator.spec.js",
              "line": 323,
              "column": 3
            },
            {
              "title": "step indicator shows step 1 active",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 771,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:18.678Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-44e942ff5afb5e2911e2",
              "file": "drive_consolidator.spec.js",
              "line": 329,
              "column": 3
            },
            {
              "title": "connect account → shows in list with name and email",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 991,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:19.464Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-319bc6157b74dbf19426",
              "file": "drive_consolidator.spec.js",
              "line": 334,
              "column": 3
            },
            {
              "title": "remove account button removes it from list",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 994,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:20.468Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-bbe2fcc08c614f6eb745",
              "file": "drive_consolidator.spec.js",
              "line": 350,
              "column": 3
            },
            {
              "title": "scan button shows toast if no accounts connected",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 808,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:21.478Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-6e59f1a4f670ffc3fba5",
              "file": "drive_consolidator.spec.js",
              "line": 366,
              "column": 3
            }
          ]
        },
        {
          "title": "Drive Consolidator — Scan",
          "file": "drive_consolidator.spec.js",
          "line": 373,
          "column": 6,
          "specs": [
            {
              "title": "scan advances to step 2 and shows file counts",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1048,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:39.314Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-ff448f7fc85c7cbcddf8",
              "file": "drive_consolidator.spec.js",
              "line": 374,
              "column": 3
            },
            {
              "title": "scan with 0 files shows count 0",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1045,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:40.377Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-36d952270d9d140e0b22",
              "file": "drive_consolidator.spec.js",
              "line": 395,
              "column": 3
            },
            {
              "title": "scan advances to step 2 and shows file counts",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1062,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:22.302Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-19817f5b3381da9970bd",
              "file": "drive_consolidator.spec.js",
              "line": 374,
              "column": 3
            },
            {
              "title": "scan with 0 files shows count 0",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 957,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:23.380Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-da93ba226bd8ed9fea8b",
              "file": "drive_consolidator.spec.js",
              "line": 395,
              "column": 3
            }
          ]
        },
        {
          "title": "Drive Consolidator — Duplicates",
          "file": "drive_consolidator.spec.js",
          "line": 412,
          "column": 6,
          "specs": [
            {
              "title": "no duplicates — shows clean message",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1610,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:41.439Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-c4b6938ff8f71e66cfb0",
              "file": "drive_consolidator.spec.js",
              "line": 436,
              "column": 3
            },
            {
              "title": "exact MD5 duplicates appear as EXACT groups",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1653,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:43.064Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-eb1e6d21f062f7b6e74f",
              "file": "drive_consolidator.spec.js",
              "line": 446,
              "column": 3
            },
            {
              "title": "expanding a dup group shows file rows",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1733,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:44.731Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-371f92aa94445062cc0e",
              "file": "drive_consolidator.spec.js",
              "line": 458,
              "column": 3
            },
            {
              "title": "auto-keep best sets first file to keep, rest to trash",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1803,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:46.482Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-383da5b947335c2c5594",
              "file": "drive_consolidator.spec.js",
              "line": 469,
              "column": 3
            },
            {
              "title": "skip all button sets all actions to skip",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1804,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:48.304Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-1d8ddd955a9493cb1c09",
              "file": "drive_consolidator.spec.js",
              "line": 482,
              "column": 3
            },
            {
              "title": "no duplicates — shows clean message",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1645,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:24.356Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-8219dca1453063d83e37",
              "file": "drive_consolidator.spec.js",
              "line": 436,
              "column": 3
            },
            {
              "title": "exact MD5 duplicates appear as EXACT groups",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1743,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:26.015Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-93f9cb22297434d90ac0",
              "file": "drive_consolidator.spec.js",
              "line": 446,
              "column": 3
            },
            {
              "title": "expanding a dup group shows file rows",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1727,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:27.775Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-40d4dbb700b819163e7a",
              "file": "drive_consolidator.spec.js",
              "line": 458,
              "column": 3
            },
            {
              "title": "auto-keep best sets first file to keep, rest to trash",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1774,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:29.528Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-56878d573d234042932d",
              "file": "drive_consolidator.spec.js",
              "line": 469,
              "column": 3
            },
            {
              "title": "skip all button sets all actions to skip",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1737,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:31.322Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-8c15b8995e8c6dacd6ff",
              "file": "drive_consolidator.spec.js",
              "line": 482,
              "column": 3
            }
          ]
        },
        {
          "title": "Drive Consolidator — Transfer",
          "file": "drive_consolidator.spec.js",
          "line": 496,
          "column": 6,
          "specs": [
            {
              "title": "transfer screen shows step 4 active",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1745,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:50.130Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-78494384895e3b02de47",
              "file": "drive_consolidator.spec.js",
              "line": 525,
              "column": 3
            },
            {
              "title": "progress bar reaches 100% after transfer completes",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1856,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:51.890Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-0db53b0140e7e9f4b8c3",
              "file": "drive_consolidator.spec.js",
              "line": 530,
              "column": 3
            },
            {
              "title": "summary card shows copied count after completion",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1776,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:53.763Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-077aefdd015ff4961e93",
              "file": "drive_consolidator.spec.js",
              "line": 536,
              "column": 3
            },
            {
              "title": "start over button returns to accounts view",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1990,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:55.556Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-060de8ad787e1964c80c",
              "file": "drive_consolidator.spec.js",
              "line": 543,
              "column": 3
            },
            {
              "title": "transfer already-in-target file → skipped",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1805,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:57.562Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-4b25bf2491736bee18b0",
              "file": "drive_consolidator.spec.js",
              "line": 551,
              "column": 3
            },
            {
              "title": "transfer screen shows step 4 active",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1646,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:33.074Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-8587708fd4f9d8176222",
              "file": "drive_consolidator.spec.js",
              "line": 525,
              "column": 3
            },
            {
              "title": "progress bar reaches 100% after transfer completes",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1862,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:34.738Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-ee476e51e247fc608f4b",
              "file": "drive_consolidator.spec.js",
              "line": 530,
              "column": 3
            },
            {
              "title": "summary card shows copied count after completion",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1913,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:36.618Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-0d9e64fe5b0fade12b71",
              "file": "drive_consolidator.spec.js",
              "line": 536,
              "column": 3
            },
            {
              "title": "start over button returns to accounts view",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1936,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:38.549Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-93cd4df9a3e55e0e8d78",
              "file": "drive_consolidator.spec.js",
              "line": 543,
              "column": 3
            },
            {
              "title": "transfer already-in-target file → skipped",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1741,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:40.501Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-2fd505e9b119c1181b94",
              "file": "drive_consolidator.spec.js",
              "line": 551,
              "column": 3
            }
          ]
        },
        {
          "title": "Drive Consolidator — PWA / SW",
          "file": "drive_consolidator.spec.js",
          "line": 582,
          "column": 6,
          "specs": [
            {
              "title": "manifest is reachable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 159,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:59.388Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-7e71653b70c4dc6cffa3",
              "file": "drive_consolidator.spec.js",
              "line": 583,
              "column": 3
            },
            {
              "title": "sw.js is served",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 130,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:59.565Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-cb61ae1e74f512a3e2eb",
              "file": "drive_consolidator.spec.js",
              "line": 591,
              "column": 3
            },
            {
              "title": "icons are served",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 137,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:59.712Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-249c4e3a9205e738bb0d",
              "file": "drive_consolidator.spec.js",
              "line": 596,
              "column": 3
            },
            {
              "title": "service worker registers without error",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1857,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:23:59.870Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-4c515f97e7e4332012e5",
              "file": "drive_consolidator.spec.js",
              "line": 603,
              "column": 3
            },
            {
              "title": "page loads while offline after first visit (SW cache)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3020,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:01.748Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-6770466834ad744d8b3d",
              "file": "drive_consolidator.spec.js",
              "line": 612,
              "column": 3
            },
            {
              "title": "manifest is reachable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 168,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:42.256Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-9ba9ec8cd56ce3befc76",
              "file": "drive_consolidator.spec.js",
              "line": 583,
              "column": 3
            },
            {
              "title": "sw.js is served",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 126,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:42.442Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-a8de2b9a411b43e4952e",
              "file": "drive_consolidator.spec.js",
              "line": 591,
              "column": 3
            },
            {
              "title": "icons are served",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 112,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:42.582Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-621efda937b699ff9e9c",
              "file": "drive_consolidator.spec.js",
              "line": 596,
              "column": 3
            },
            {
              "title": "service worker registers without error",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1907,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:42.710Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-a2cbaf5de2edba4e769a",
              "file": "drive_consolidator.spec.js",
              "line": 603,
              "column": 3
            },
            {
              "title": "page loads while offline after first visit (SW cache)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2447,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:44.641Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "a6ab13040c0baac167d8-64b20c6f158addc13ce1",
              "file": "drive_consolidator.spec.js",
              "line": 612,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "expense.spec.js",
      "file": "expense.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Expenses — Smoke",
          "file": "expense.spec.js",
          "line": 9,
          "column": 6,
          "specs": [
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1404,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:04.806Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-3bf1fb454096f73d81f8",
              "file": "expense.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 174,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:06.228Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-d5316b6e995b7790fe42",
              "file": "expense.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 215,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:06.425Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-22d3fda65423125f291f",
              "file": "expense.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1334,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:47.122Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-7d1f2eea2388ccca6d02",
              "file": "expense.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 198,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:48.482Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-1da934617a4d1cda8788",
              "file": "expense.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 198,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:48.703Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-d7fecb204bf6e208d449",
              "file": "expense.spec.js",
              "line": 22,
              "column": 3
            }
          ]
        },
        {
          "title": "Expenses — HTML Structure",
          "file": "expense.spec.js",
          "line": 29,
          "column": 6,
          "specs": [
            {
              "title": "all screens present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:06.660Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-cf961a4a22ce7b4adf00",
              "file": "expense.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "key functions present in source",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:06.738Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-1e43735bf45a18708dc4",
              "file": "expense.spec.js",
              "line": 50,
              "column": 3
            },
            {
              "title": "hub navigation is present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:06.757Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-fa4747e31770727c09ff",
              "file": "expense.spec.js",
              "line": 59,
              "column": 3
            },
            {
              "title": "push notification support present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:06.773Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-6626a912c1ac516dab54",
              "file": "expense.spec.js",
              "line": 64,
              "column": 3
            },
            {
              "title": "custodian advances integration present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:06.785Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-ea60ed3da9da31148844",
              "file": "expense.spec.js",
              "line": 69,
              "column": 3
            },
            {
              "title": "impress screen present for Impress Reports",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:06.795Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-4f6fe6df41f7cbbc8293",
              "file": "expense.spec.js",
              "line": 74,
              "column": 3
            },
            {
              "title": "all screens present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 7,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:48.920Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-543108dd5b722e4b30e7",
              "file": "expense.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "key functions present in source",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:48.995Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-4af52f23743a9ad7d894",
              "file": "expense.spec.js",
              "line": 50,
              "column": 3
            },
            {
              "title": "hub navigation is present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:49.045Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-7e3373e789c02c52b233",
              "file": "expense.spec.js",
              "line": 59,
              "column": 3
            },
            {
              "title": "push notification support present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:49.068Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-efdd0c31fb75f3eb1693",
              "file": "expense.spec.js",
              "line": 64,
              "column": 3
            },
            {
              "title": "custodian advances integration present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:49.083Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-14bcda3901c84ce603fd",
              "file": "expense.spec.js",
              "line": 69,
              "column": 3
            },
            {
              "title": "impress screen present for Impress Reports",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:49.097Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "2399d47327fc24358745-e4d36895cc6108ba501b",
              "file": "expense.spec.js",
              "line": 74,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "finance.spec.js",
      "file": "finance.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Finance — Smoke",
          "file": "finance.spec.js",
          "line": 9,
          "column": 6,
          "specs": [
            {
              "title": "page loads",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 244,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 202,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:12:24",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 24,
                          "line": 12
                        },
                        "snippet": "  10 |   test('page loads', async ({ page }) => {\n  11 |     const resp = await page.goto(BASE_URL);\n> 12 |     expect([200, 302]).toContain(resp?.status());\n     |                        ^\n  13 |     await expect(page.locator('body')).not.toBeEmpty();\n  14 |   });\n  15 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 24,
                            "line": 12
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  10 |   test('page loads', async ({ page }) => {\n  11 |     const resp = await page.goto(BASE_URL);\n> 12 |     expect([200, 302]).toContain(resp?.status());\n     |                        ^\n  13 |     await expect(page.locator('body')).not.toBeEmpty();\n  14 |   });\n  15 |\n    at /var/www/360lm/tests/finance.spec.js:12:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:06.812Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-page-loads-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-page-loads-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 24,
                        "line": 12
                      }
                    },
                    {
                      "workerIndex": 245,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 301,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:12:24",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 24,
                          "line": 12
                        },
                        "snippet": "  10 |   test('page loads', async ({ page }) => {\n  11 |     const resp = await page.goto(BASE_URL);\n> 12 |     expect([200, 302]).toContain(resp?.status());\n     |                        ^\n  13 |     await expect(page.locator('body')).not.toBeEmpty();\n  14 |   });\n  15 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 24,
                            "line": 12
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  10 |   test('page loads', async ({ page }) => {\n  11 |     const resp = await page.goto(BASE_URL);\n> 12 |     expect([200, 302]).toContain(resp?.status());\n     |                        ^\n  13 |     await expect(page.locator('body')).not.toBeEmpty();\n  14 |   });\n  15 |\n    at /var/www/360lm/tests/finance.spec.js:12:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:24:07.826Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-page-loads-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-page-loads-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 24,
                        "line": 12
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-25b3bd6163172e540bae",
              "file": "finance.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 246,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:18:28",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 28,
                          "line": 18
                        },
                        "snippet": "  16 |   test('manifest.json returns 200', async ({ page }) => {\n  17 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 18 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  19 |   });\n  20 |\n  21 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 28,
                            "line": 18
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  16 |   test('manifest.json returns 200', async ({ page }) => {\n  17 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 18 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  19 |   });\n  20 |\n  21 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/finance.spec.js:18:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:09.108Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 28,
                        "line": 18
                      }
                    },
                    {
                      "workerIndex": 247,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 288,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:18:28",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 28,
                          "line": 18
                        },
                        "snippet": "  16 |   test('manifest.json returns 200', async ({ page }) => {\n  17 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 18 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  19 |   });\n  20 |\n  21 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 28,
                            "line": 18
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  16 |   test('manifest.json returns 200', async ({ page }) => {\n  17 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 18 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  19 |   });\n  20 |\n  21 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/finance.spec.js:18:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:24:10.358Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 28,
                        "line": 18
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-ec0576e348622d61c3ea",
              "file": "finance.spec.js",
              "line": 16,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 248,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 274,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:24:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 24
                        },
                        "snippet": "  22 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  23 |     const text = await resp?.text() || '';\n> 24 |     expect(text).toMatch(/360f2-v\\d+/);\n     |                  ^\n  25 |   });\n  26 | });\n  27 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 24
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  22 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  23 |     const text = await resp?.text() || '';\n> 24 |     expect(text).toMatch(/360f2-v\\d+/);\n     |                  ^\n  25 |   });\n  26 | });\n  27 |\n    at /var/www/360lm/tests/finance.spec.js:24:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:11.618Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-sw-js-is-versioned-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-sw-js-is-versioned-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 24
                      }
                    },
                    {
                      "workerIndex": 249,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 302,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:24:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 24
                        },
                        "snippet": "  22 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  23 |     const text = await resp?.text() || '';\n> 24 |     expect(text).toMatch(/360f2-v\\d+/);\n     |                  ^\n  25 |   });\n  26 | });\n  27 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 24
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  22 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  23 |     const text = await resp?.text() || '';\n> 24 |     expect(text).toMatch(/360f2-v\\d+/);\n     |                  ^\n  25 |   });\n  26 | });\n  27 |\n    at /var/www/360lm/tests/finance.spec.js:24:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:24:12.817Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-sw-js-is-versioned-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-sw-js-is-versioned-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 24
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-eb14c8aaf0a9ef939be6",
              "file": "finance.spec.js",
              "line": 21,
              "column": 3
            },
            {
              "title": "page loads",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1251,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 230,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:12:24",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 24,
                          "line": 12
                        },
                        "snippet": "  10 |   test('page loads', async ({ page }) => {\n  11 |     const resp = await page.goto(BASE_URL);\n> 12 |     expect([200, 302]).toContain(resp?.status());\n     |                        ^\n  13 |     await expect(page.locator('body')).not.toBeEmpty();\n  14 |   });\n  15 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 24,
                            "line": 12
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  10 |   test('page loads', async ({ page }) => {\n  11 |     const resp = await page.goto(BASE_URL);\n> 12 |     expect([200, 302]).toContain(resp?.status());\n     |                        ^\n  13 |     await expect(page.locator('body')).not.toBeEmpty();\n  14 |   });\n  15 |\n    at /var/www/360lm/tests/finance.spec.js:12:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:49.117Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-page-loads-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-page-loads-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 24,
                        "line": 12
                      }
                    },
                    {
                      "workerIndex": 1252,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 346,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:12:24",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 24,
                          "line": 12
                        },
                        "snippet": "  10 |   test('page loads', async ({ page }) => {\n  11 |     const resp = await page.goto(BASE_URL);\n> 12 |     expect([200, 302]).toContain(resp?.status());\n     |                        ^\n  13 |     await expect(page.locator('body')).not.toBeEmpty();\n  14 |   });\n  15 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 24,
                            "line": 12
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  10 |   test('page loads', async ({ page }) => {\n  11 |     const resp = await page.goto(BASE_URL);\n> 12 |     expect([200, 302]).toContain(resp?.status());\n     |                        ^\n  13 |     await expect(page.locator('body')).not.toBeEmpty();\n  14 |   });\n  15 |\n    at /var/www/360lm/tests/finance.spec.js:12:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:23:50.435Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-page-loads-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-page-loads-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 24,
                        "line": 12
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-75af53f0348f13579015",
              "file": "finance.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1253,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 324,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:18:28",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 28,
                          "line": 18
                        },
                        "snippet": "  16 |   test('manifest.json returns 200', async ({ page }) => {\n  17 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 18 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  19 |   });\n  20 |\n  21 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 28,
                            "line": 18
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  16 |   test('manifest.json returns 200', async ({ page }) => {\n  17 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 18 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  19 |   });\n  20 |\n  21 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/finance.spec.js:18:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:51.887Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 28,
                        "line": 18
                      }
                    },
                    {
                      "workerIndex": 1254,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 320,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:18:28",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 28,
                          "line": 18
                        },
                        "snippet": "  16 |   test('manifest.json returns 200', async ({ page }) => {\n  17 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 18 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  19 |   });\n  20 |\n  21 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 28,
                            "line": 18
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  16 |   test('manifest.json returns 200', async ({ page }) => {\n  17 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 18 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  19 |   });\n  20 |\n  21 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/finance.spec.js:18:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:23:53.379Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 28,
                        "line": 18
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-a7a741bf103a9af8a1c9",
              "file": "finance.spec.js",
              "line": 16,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1255,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 321,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:24:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 24
                        },
                        "snippet": "  22 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  23 |     const text = await resp?.text() || '';\n> 24 |     expect(text).toMatch(/360f2-v\\d+/);\n     |                  ^\n  25 |   });\n  26 | });\n  27 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 24
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  22 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  23 |     const text = await resp?.text() || '';\n> 24 |     expect(text).toMatch(/360f2-v\\d+/);\n     |                  ^\n  25 |   });\n  26 | });\n  27 |\n    at /var/www/360lm/tests/finance.spec.js:24:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:54.868Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-sw-js-is-versioned-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-sw-js-is-versioned-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 24
                      }
                    },
                    {
                      "workerIndex": 1256,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 351,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:24:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 24
                        },
                        "snippet": "  22 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  23 |     const text = await resp?.text() || '';\n> 24 |     expect(text).toMatch(/360f2-v\\d+/);\n     |                  ^\n  25 |   });\n  26 | });\n  27 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 24
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360f2-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  22 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  23 |     const text = await resp?.text() || '';\n> 24 |     expect(text).toMatch(/360f2-v\\d+/);\n     |                  ^\n  25 |   });\n  26 | });\n  27 |\n    at /var/www/360lm/tests/finance.spec.js:24:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:23:56.318Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-sw-js-is-versioned-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-Smoke-sw-js-is-versioned-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 24
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-1b8e3d83fb98977b3c1f",
              "file": "finance.spec.js",
              "line": 21,
              "column": 3
            }
          ]
        },
        {
          "title": "Finance — HTML Structure",
          "file": "finance.spec.js",
          "line": 28,
          "column": 6,
          "specs": [
            {
              "title": "main topbar and header present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 250,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 18,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:39:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 39
                        },
                        "snippet": "  37 |\n  38 |   test('main topbar and header present', () => {\n> 39 |     expect(html).toContain('360° Finance');\n     |                  ^\n  40 |     expect(html).toContain('class=\"topbar\"');\n  41 |   });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  37 |\n  38 |   test('main topbar and header present', () => {\n> 39 |     expect(html).toContain('360° Finance');\n     |                  ^\n  40 |     expect(html).toContain('class=\"topbar\"');\n  41 |   });\n  42 |\n    at /var/www/360lm/tests/finance.spec.js:39:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:14.066Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Str-5596a-n-topbar-and-header-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 39
                      }
                    },
                    {
                      "workerIndex": 251,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:39:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 39
                        },
                        "snippet": "  37 |\n  38 |   test('main topbar and header present', () => {\n> 39 |     expect(html).toContain('360° Finance');\n     |                  ^\n  40 |     expect(html).toContain('class=\"topbar\"');\n  41 |   });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  37 |\n  38 |   test('main topbar and header present', () => {\n> 39 |     expect(html).toContain('360° Finance');\n     |                  ^\n  40 |     expect(html).toContain('class=\"topbar\"');\n  41 |   });\n  42 |\n    at /var/www/360lm/tests/finance.spec.js:39:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:24:14.906Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Str-5596a-n-topbar-and-header-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 39
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-ad4606f74ccfdb5dca2e",
              "file": "finance.spec.js",
              "line": 38,
              "column": 3
            },
            {
              "title": "hub navigation button present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 252,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:44:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 44
                        },
                        "snippet": "  42 |\n  43 |   test('hub navigation button present', () => {\n> 44 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  45 |     expect(html).toContain('🏡');\n  46 |   });\n  47 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 44
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  42 |\n  43 |   test('hub navigation button present', () => {\n> 44 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  45 |     expect(html).toContain('🏡');\n  46 |   });\n  47 |\n    at /var/www/360lm/tests/finance.spec.js:44:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:15.796Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Str-aa382-b-navigation-button-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 253,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 14,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:44:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 44
                        },
                        "snippet": "  42 |\n  43 |   test('hub navigation button present', () => {\n> 44 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  45 |     expect(html).toContain('🏡');\n  46 |   });\n  47 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 44
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  42 |\n  43 |   test('hub navigation button present', () => {\n> 44 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  45 |     expect(html).toContain('🏡');\n  46 |   });\n  47 |\n    at /var/www/360lm/tests/finance.spec.js:44:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:24:16.701Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Str-aa382-b-navigation-button-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-05135786a7da2e33f662",
              "file": "finance.spec.js",
              "line": 43,
              "column": 3
            },
            {
              "title": "balance cards present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 254,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:49:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 49
                        },
                        "snippet": "  47 |\n  48 |   test('balance cards present', () => {\n> 49 |     expect(html).toContain('id=\"bCash\"');\n     |                  ^\n  50 |     expect(html).toContain('id=\"bBank\"');\n  51 |     expect(html).toContain('id=\"bAdv\"');\n  52 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  47 |\n  48 |   test('balance cards present', () => {\n> 49 |     expect(html).toContain('id=\"bCash\"');\n     |                  ^\n  50 |     expect(html).toContain('id=\"bBank\"');\n  51 |     expect(html).toContain('id=\"bAdv\"');\n  52 |   });\n    at /var/www/360lm/tests/finance.spec.js:49:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:17.694Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Structure-balance-cards-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 49
                      }
                    },
                    {
                      "workerIndex": 255,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:49:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 49
                        },
                        "snippet": "  47 |\n  48 |   test('balance cards present', () => {\n> 49 |     expect(html).toContain('id=\"bCash\"');\n     |                  ^\n  50 |     expect(html).toContain('id=\"bBank\"');\n  51 |     expect(html).toContain('id=\"bAdv\"');\n  52 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  47 |\n  48 |   test('balance cards present', () => {\n> 49 |     expect(html).toContain('id=\"bCash\"');\n     |                  ^\n  50 |     expect(html).toContain('id=\"bBank\"');\n  51 |     expect(html).toContain('id=\"bAdv\"');\n  52 |   });\n    at /var/www/360lm/tests/finance.spec.js:49:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:24:18.543Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Structure-balance-cards-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 49
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-01a94bb96f3c6edbe454",
              "file": "finance.spec.js",
              "line": 48,
              "column": 3
            },
            {
              "title": "main topbar and header present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1257,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 19,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:39:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 39
                        },
                        "snippet": "  37 |\n  38 |   test('main topbar and header present', () => {\n> 39 |     expect(html).toContain('360° Finance');\n     |                  ^\n  40 |     expect(html).toContain('class=\"topbar\"');\n  41 |   });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  37 |\n  38 |   test('main topbar and header present', () => {\n> 39 |     expect(html).toContain('360° Finance');\n     |                  ^\n  40 |     expect(html).toContain('class=\"topbar\"');\n  41 |   });\n  42 |\n    at /var/www/360lm/tests/finance.spec.js:39:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:57.803Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Str-5596a-n-topbar-and-header-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 39
                      }
                    },
                    {
                      "workerIndex": 1258,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:39:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 39
                        },
                        "snippet": "  37 |\n  38 |   test('main topbar and header present', () => {\n> 39 |     expect(html).toContain('360° Finance');\n     |                  ^\n  40 |     expect(html).toContain('class=\"topbar\"');\n  41 |   });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"360° Finance\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  37 |\n  38 |   test('main topbar and header present', () => {\n> 39 |     expect(html).toContain('360° Finance');\n     |                  ^\n  40 |     expect(html).toContain('class=\"topbar\"');\n  41 |   });\n  42 |\n    at /var/www/360lm/tests/finance.spec.js:39:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:23:58.833Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Str-5596a-n-topbar-and-header-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 39
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-2e358c1a1e843da9fe8d",
              "file": "finance.spec.js",
              "line": 38,
              "column": 3
            },
            {
              "title": "hub navigation button present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1259,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 16,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:44:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 44
                        },
                        "snippet": "  42 |\n  43 |   test('hub navigation button present', () => {\n> 44 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  45 |     expect(html).toContain('🏡');\n  46 |   });\n  47 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 44
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  42 |\n  43 |   test('hub navigation button present', () => {\n> 44 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  45 |     expect(html).toContain('🏡');\n  46 |   });\n  47 |\n    at /var/www/360lm/tests/finance.spec.js:44:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:23:59.885Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Str-aa382-b-navigation-button-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 1260,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 16,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:44:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 44
                        },
                        "snippet": "  42 |\n  43 |   test('hub navigation button present', () => {\n> 44 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  45 |     expect(html).toContain('🏡');\n  46 |   });\n  47 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 44
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  42 |\n  43 |   test('hub navigation button present', () => {\n> 44 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  45 |     expect(html).toContain('🏡');\n  46 |   });\n  47 |\n    at /var/www/360lm/tests/finance.spec.js:44:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:24:01.123Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Str-aa382-b-navigation-button-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-e6cebf9d37fe7c6adb59",
              "file": "finance.spec.js",
              "line": 43,
              "column": 3
            },
            {
              "title": "balance cards present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1261,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 18,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:49:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 49
                        },
                        "snippet": "  47 |\n  48 |   test('balance cards present', () => {\n> 49 |     expect(html).toContain('id=\"bCash\"');\n     |                  ^\n  50 |     expect(html).toContain('id=\"bBank\"');\n  51 |     expect(html).toContain('id=\"bAdv\"');\n  52 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  47 |\n  48 |   test('balance cards present', () => {\n> 49 |     expect(html).toContain('id=\"bCash\"');\n     |                  ^\n  50 |     expect(html).toContain('id=\"bBank\"');\n  51 |     expect(html).toContain('id=\"bAdv\"');\n  52 |   });\n    at /var/www/360lm/tests/finance.spec.js:49:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:24:02.822Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Structure-balance-cards-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 49
                      }
                    },
                    {
                      "workerIndex": 1262,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 19,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/finance.spec.js:49:18",
                        "location": {
                          "file": "/var/www/360lm/tests/finance.spec.js",
                          "column": 18,
                          "line": 49
                        },
                        "snippet": "  47 |\n  48 |   test('balance cards present', () => {\n> 49 |     expect(html).toContain('id=\"bCash\"');\n     |                  ^\n  50 |     expect(html).toContain('id=\"bBank\"');\n  51 |     expect(html).toContain('id=\"bAdv\"');\n  52 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/finance.spec.js",
                            "column": 18,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"bCash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  47 |\n  48 |   test('balance cards present', () => {\n> 49 |     expect(html).toContain('id=\"bCash\"');\n     |                  ^\n  50 |     expect(html).toContain('id=\"bBank\"');\n  51 |     expect(html).toContain('id=\"bAdv\"');\n  52 |   });\n    at /var/www/360lm/tests/finance.spec.js:49:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:24:04.597Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/finance-Finance-—-HTML-Structure-balance-cards-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/finance.spec.js",
                        "column": 18,
                        "line": 49
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c783d6f0c7990efc0ac2-2a2ff6351d4e9a448893",
              "file": "finance.spec.js",
              "line": 48,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "fundcustodian.spec.js",
      "file": "fundcustodian.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Auth Gate",
          "file": "fundcustodian.spec.js",
          "line": 48,
          "column": 6,
          "specs": [
            {
              "title": "no session → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 256,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8286,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/fundcustodian.spec.js:51:16",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 16,
                          "line": 51
                        },
                        "snippet": "  49 |   test('no session → redirects to /hub/', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  52 |     expect(page.url()).toContain('/hub/');\n  53 |   });\n  54 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 16,
                            "line": 51
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  49 |   test('no session → redirects to /hub/', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  52 |     expect(page.url()).toContain('/hub/');\n  53 |   });\n  54 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:51:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:19.503Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-session-→-redirects-to-hub--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-session-→-redirects-to-hub--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 16,
                        "line": 51
                      }
                    },
                    {
                      "workerIndex": 257,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8250,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/fundcustodian.spec.js:51:16",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 16,
                          "line": 51
                        },
                        "snippet": "  49 |   test('no session → redirects to /hub/', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  52 |     expect(page.url()).toContain('/hub/');\n  53 |   });\n  54 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 16,
                            "line": 51
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  49 |   test('no session → redirects to /hub/', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  52 |     expect(page.url()).toContain('/hub/');\n  53 |   });\n  54 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:51:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:24:28.816Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-session-→-redirects-to-hub--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-session-→-redirects-to-hub--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 16,
                        "line": 51
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-3f3531e5c6f0a7eb185d",
              "file": "fundcustodian.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "expired session (>12h) → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 258,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8288,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/fundcustodian.spec.js:59:16",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 16,
                          "line": 59
                        },
                        "snippet": "  57 |     await injectSession(page, expired);\n  58 |     await page.goto(BASE_URL);\n> 59 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  60 |     expect(page.url()).toContain('/hub/');\n  61 |   });\n  62 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 16,
                            "line": 59
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  57 |     await injectSession(page, expired);\n  58 |     await page.goto(BASE_URL);\n> 59 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  60 |     expect(page.url()).toContain('/hub/');\n  61 |   });\n  62 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:59:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:38.061Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-ex-cf7f6-ion-12h-→-redirects-to-hub--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-ex-cf7f6-ion-12h-→-redirects-to-hub--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 16,
                        "line": 59
                      }
                    },
                    {
                      "workerIndex": 259,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8281,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/fundcustodian.spec.js:59:16",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 16,
                          "line": 59
                        },
                        "snippet": "  57 |     await injectSession(page, expired);\n  58 |     await page.goto(BASE_URL);\n> 59 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  60 |     expect(page.url()).toContain('/hub/');\n  61 |   });\n  62 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 16,
                            "line": 59
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  57 |     await injectSession(page, expired);\n  58 |     await page.goto(BASE_URL);\n> 59 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  60 |     expect(page.url()).toContain('/hub/');\n  61 |   });\n  62 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:59:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:24:47.374Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-ex-cf7f6-ion-12h-→-redirects-to-hub--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-ex-cf7f6-ion-12h-→-redirects-to-hub--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 16,
                        "line": 59
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-86b36f68a8f23d935137",
              "file": "fundcustodian.spec.js",
              "line": 55,
              "column": 3
            },
            {
              "title": "non-custodian employee → Access Restricted screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 260,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8295,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:66:58",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 58,
                          "line": 66
                        },
                        "snippet": "  64 |     await injectSession(page, SESSION.mukesh);\n  65 |     await page.goto(BASE_URL);\n> 66 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n     |                                                          ^\n  67 |     await expect(page.locator('text=fund custodians')).toBeVisible();\n  68 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  69 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 58,
                            "line": 66
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  64 |     await injectSession(page, SESSION.mukesh);\n  65 |     await page.goto(BASE_URL);\n> 66 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n     |                                                          ^\n  67 |     await expect(page.locator('text=fund custodians')).toBeVisible();\n  68 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  69 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:66:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:24:56.690Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-8a239--→-Access-Restricted-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-8a239--→-Access-Restricted-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 58,
                        "line": 66
                      }
                    },
                    {
                      "workerIndex": 261,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8292,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:66:58",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 58,
                          "line": 66
                        },
                        "snippet": "  64 |     await injectSession(page, SESSION.mukesh);\n  65 |     await page.goto(BASE_URL);\n> 66 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n     |                                                          ^\n  67 |     await expect(page.locator('text=fund custodians')).toBeVisible();\n  68 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  69 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 58,
                            "line": 66
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  64 |     await injectSession(page, SESSION.mukesh);\n  65 |     await page.goto(BASE_URL);\n> 66 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n     |                                                          ^\n  67 |     await expect(page.locator('text=fund custodians')).toBeVisible();\n  68 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  69 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:66:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:25:06.264Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-8a239--→-Access-Restricted-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-8a239--→-Access-Restricted-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 58,
                        "line": 66
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-d777dbfe693f198391f4",
              "file": "fundcustodian.spec.js",
              "line": 63,
              "column": 3
            },
            {
              "title": "no session → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1263,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8342,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/fundcustodian.spec.js:51:16",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 16,
                          "line": 51
                        },
                        "snippet": "  49 |   test('no session → redirects to /hub/', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  52 |     expect(page.url()).toContain('/hub/');\n  53 |   });\n  54 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 16,
                            "line": 51
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  49 |   test('no session → redirects to /hub/', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  52 |     expect(page.url()).toContain('/hub/');\n  53 |   });\n  54 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:51:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:24:06.067Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-session-→-redirects-to-hub--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-session-→-redirects-to-hub--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 16,
                        "line": 51
                      }
                    },
                    {
                      "workerIndex": 1264,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8280,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/fundcustodian.spec.js:51:16",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 16,
                          "line": 51
                        },
                        "snippet": "  49 |   test('no session → redirects to /hub/', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  52 |     expect(page.url()).toContain('/hub/');\n  53 |   });\n  54 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 16,
                            "line": 51
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  49 |   test('no session → redirects to /hub/', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  52 |     expect(page.url()).toContain('/hub/');\n  53 |   });\n  54 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:51:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:24:15.425Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-session-→-redirects-to-hub--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-session-→-redirects-to-hub--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 16,
                        "line": 51
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-4941793c034feaa4c8d4",
              "file": "fundcustodian.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "expired session (>12h) → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1265,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8270,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/fundcustodian.spec.js:59:16",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 16,
                          "line": 59
                        },
                        "snippet": "  57 |     await injectSession(page, expired);\n  58 |     await page.goto(BASE_URL);\n> 59 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  60 |     expect(page.url()).toContain('/hub/');\n  61 |   });\n  62 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 16,
                            "line": 59
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  57 |     await injectSession(page, expired);\n  58 |     await page.goto(BASE_URL);\n> 59 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  60 |     expect(page.url()).toContain('/hub/');\n  61 |   });\n  62 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:59:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:24:24.671Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-ex-cf7f6-ion-12h-→-redirects-to-hub--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-ex-cf7f6-ion-12h-→-redirects-to-hub--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 16,
                        "line": 59
                      }
                    },
                    {
                      "workerIndex": 1266,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8267,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/fundcustodian.spec.js:59:16",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 16,
                          "line": 59
                        },
                        "snippet": "  57 |     await injectSession(page, expired);\n  58 |     await page.goto(BASE_URL);\n> 59 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  60 |     expect(page.url()).toContain('/hub/');\n  61 |   });\n  62 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 16,
                            "line": 59
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  57 |     await injectSession(page, expired);\n  58 |     await page.goto(BASE_URL);\n> 59 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  60 |     expect(page.url()).toContain('/hub/');\n  61 |   });\n  62 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:59:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:24:33.979Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-ex-cf7f6-ion-12h-→-redirects-to-hub--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-ex-cf7f6-ion-12h-→-redirects-to-hub--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 16,
                        "line": 59
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-be7df170420993cdf364",
              "file": "fundcustodian.spec.js",
              "line": 55,
              "column": 3
            },
            {
              "title": "non-custodian employee → Access Restricted screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1267,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8244,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:66:58",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 58,
                          "line": 66
                        },
                        "snippet": "  64 |     await injectSession(page, SESSION.mukesh);\n  65 |     await page.goto(BASE_URL);\n> 66 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n     |                                                          ^\n  67 |     await expect(page.locator('text=fund custodians')).toBeVisible();\n  68 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  69 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 58,
                            "line": 66
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  64 |     await injectSession(page, SESSION.mukesh);\n  65 |     await page.goto(BASE_URL);\n> 66 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n     |                                                          ^\n  67 |     await expect(page.locator('text=fund custodians')).toBeVisible();\n  68 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  69 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:66:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:24:43.327Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-8a239--→-Access-Restricted-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-8a239--→-Access-Restricted-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 58,
                        "line": 66
                      }
                    },
                    {
                      "workerIndex": 1268,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8322,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:66:58",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 58,
                          "line": 66
                        },
                        "snippet": "  64 |     await injectSession(page, SESSION.mukesh);\n  65 |     await page.goto(BASE_URL);\n> 66 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n     |                                                          ^\n  67 |     await expect(page.locator('text=fund custodians')).toBeVisible();\n  68 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  69 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 58,
                            "line": 66
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  64 |     await injectSession(page, SESSION.mukesh);\n  65 |     await page.goto(BASE_URL);\n> 66 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 8000 });\n     |                                                          ^\n  67 |     await expect(page.locator('text=fund custodians')).toBeVisible();\n  68 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  69 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:66:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:24:52.591Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-8a239--→-Access-Restricted-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Auth-Gate-no-8a239--→-Access-Restricted-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 58,
                        "line": 66
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-f857b668291e7e17d3b0",
              "file": "fundcustodian.spec.js",
              "line": 63,
              "column": 3
            }
          ]
        },
        {
          "title": "Dashboard — shambhu (non-admin)",
          "file": "fundcustodian.spec.js",
          "line": 75,
          "column": 6,
          "specs": [
            {
              "title": "balance card renders and shows shambhu name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 262,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10264,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:25:15.731Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--71973-ders-and-shows-shambhu-name-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--71973-ders-and-shows-shambhu-name-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 263,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10268,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:25:27.132Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--71973-ders-and-shows-shambhu-name-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--71973-ders-and-shows-shambhu-name-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-21a11ed89b787d18d3dc",
              "file": "fundcustodian.spec.js",
              "line": 82,
              "column": 3
            },
            {
              "title": "no pending transfers banner shown for shambhu",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 264,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10276,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:25:38.437Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--171e9-rs-banner-shown-for-shambhu-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--171e9-rs-banner-shown-for-shambhu-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 265,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10296,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:25:49.757Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--171e9-rs-banner-shown-for-shambhu-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--171e9-rs-banner-shown-for-shambhu-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-f9f72838429f556e8a62",
              "file": "fundcustodian.spec.js",
              "line": 88,
              "column": 3
            },
            {
              "title": "outstanding advances section is visible",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 266,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10274,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:26:01.229Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--d41af-advances-section-is-visible-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--d41af-advances-section-is-visible-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 267,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10231,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:26:12.532Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--d41af-advances-section-is-visible-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--d41af-advances-section-is-visible-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-3c2e52fa6ce9b7aae36f",
              "file": "fundcustodian.spec.js",
              "line": 92,
              "column": 3
            },
            {
              "title": "admin balance grid is NOT shown for non-admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 268,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10241,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:26:23.751Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--00c11--is-NOT-shown-for-non-admin-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--00c11--is-NOT-shown-for-non-admin-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 269,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10241,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:26:34.946Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--00c11--is-NOT-shown-for-non-admin-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--00c11--is-NOT-shown-for-non-admin-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-b40003816c0568de9e44",
              "file": "fundcustodian.spec.js",
              "line": 99,
              "column": 3
            },
            {
              "title": "manage custodian button hidden for non-admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 270,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10256,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:26:46.193Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c1427-button-hidden-for-non-admin-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c1427-button-hidden-for-non-admin-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 271,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:26:57.423Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c1427-button-hidden-for-non-admin-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c1427-button-hidden-for-non-admin-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-29b36b85b84120645af2",
              "file": "fundcustodian.spec.js",
              "line": 104,
              "column": 3
            },
            {
              "title": "bottom nav has 3 tabs (advance merged into transfer)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 272,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10258,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:27:08.746Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--946e6-vance-merged-into-transfer--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--946e6-vance-merged-into-transfer--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 273,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10255,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:27:20.148Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--946e6-vance-merged-into-transfer--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--946e6-vance-merged-into-transfer--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-1cab052ea3e78713e6b5",
              "file": "fundcustodian.spec.js",
              "line": 108,
              "column": 3
            },
            {
              "title": "recent transfers card loads",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 274,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10264,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:27:31.451Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--ea58b-recent-transfers-card-loads-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--ea58b-recent-transfers-card-loads-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 275,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10263,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:27:42.767Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--ea58b-recent-transfers-card-loads-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--ea58b-recent-transfers-card-loads-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-8b27d6cd9dc8e53fea06",
              "file": "fundcustodian.spec.js",
              "line": 114,
              "column": 3
            },
            {
              "title": "balance card renders and shows shambhu name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1269,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10405,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:25:02.674Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--71973-ders-and-shows-shambhu-name-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--71973-ders-and-shows-shambhu-name-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 1270,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10252,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:25:14.196Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--71973-ders-and-shows-shambhu-name-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--71973-ders-and-shows-shambhu-name-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-312b40c465c7720f813d",
              "file": "fundcustodian.spec.js",
              "line": 82,
              "column": 3
            },
            {
              "title": "no pending transfers banner shown for shambhu",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1271,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10259,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:25:25.516Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--171e9-rs-banner-shown-for-shambhu-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--171e9-rs-banner-shown-for-shambhu-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 1272,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10261,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:25:36.777Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--171e9-rs-banner-shown-for-shambhu-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--171e9-rs-banner-shown-for-shambhu-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-6e731fbc13a8774e3e98",
              "file": "fundcustodian.spec.js",
              "line": 88,
              "column": 3
            },
            {
              "title": "outstanding advances section is visible",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1273,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10249,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:25:48.113Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--d41af-advances-section-is-visible-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--d41af-advances-section-is-visible-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 1274,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10259,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:25:59.469Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--d41af-advances-section-is-visible-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--d41af-advances-section-is-visible-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-b638704a578a8caf5b7b",
              "file": "fundcustodian.spec.js",
              "line": 92,
              "column": 3
            },
            {
              "title": "admin balance grid is NOT shown for non-admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1275,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10249,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:26:10.804Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--00c11--is-NOT-shown-for-non-admin-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--00c11--is-NOT-shown-for-non-admin-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 1276,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10275,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:26:22.078Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--00c11--is-NOT-shown-for-non-admin-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--00c11--is-NOT-shown-for-non-admin-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-e973e4f214bc25080cda",
              "file": "fundcustodian.spec.js",
              "line": 99,
              "column": 3
            },
            {
              "title": "manage custodian button hidden for non-admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1277,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:26:33.446Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c1427-button-hidden-for-non-admin-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c1427-button-hidden-for-non-admin-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 1278,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10241,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:26:44.674Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c1427-button-hidden-for-non-admin-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c1427-button-hidden-for-non-admin-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-5170797acb4aa05bf1b9",
              "file": "fundcustodian.spec.js",
              "line": 104,
              "column": 3
            },
            {
              "title": "bottom nav has 3 tabs (advance merged into transfer)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1279,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10274,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:26:56.056Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--946e6-vance-merged-into-transfer--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--946e6-vance-merged-into-transfer--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 1280,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10251,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:27:07.331Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--946e6-vance-merged-into-transfer--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--946e6-vance-merged-into-transfer--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-f4a688f78f0b30d79180",
              "file": "fundcustodian.spec.js",
              "line": 108,
              "column": 3
            },
            {
              "title": "recent transfers card loads",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1281,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10284,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:27:18.676Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--ea58b-recent-transfers-card-loads-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--ea58b-recent-transfers-card-loads-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    },
                    {
                      "workerIndex": 1282,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10257,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 79
                        },
                        "snippet": "  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 79
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  77 |     await injectSession(page, SESSION.shambhu);\n  78 |     await page.goto(BASE_URL);\n> 79 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n     |                                               ^\n  80 |   });\n  81 |\n  82 |   test('balance card renders and shows shambhu name', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:79:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:27:30.118Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--ea58b-recent-transfers-card-loads-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--ea58b-recent-transfers-card-loads-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 79
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-0b597b9896607af0ed81",
              "file": "fundcustodian.spec.js",
              "line": 114,
              "column": 3
            }
          ]
        },
        {
          "title": "Dashboard — harish (admin, 2 pending)",
          "file": "fundcustodian.spec.js",
          "line": 123,
          "column": 6,
          "specs": [
            {
              "title": "pending banner appears with count 2",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 276,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10305,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:27:54.055Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c5dfb-banner-appears-with-count-2-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c5dfb-banner-appears-with-count-2-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 277,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10271,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:28:05.469Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c5dfb-banner-appears-with-count-2-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c5dfb-banner-appears-with-count-2-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-0cf52fe6a4355dfb045a",
              "file": "fundcustodian.spec.js",
              "line": 131,
              "column": 3
            },
            {
              "title": "pending banner shows from-name and amount for both",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 278,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10304,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:28:16.754Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--f5e84-om-name-and-amount-for-both-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--f5e84-om-name-and-amount-for-both-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 279,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10283,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:28:28.093Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--f5e84-om-name-and-amount-for-both-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--f5e84-om-name-and-amount-for-both-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-b04a183312ba73e5a11d",
              "file": "fundcustodian.spec.js",
              "line": 137,
              "column": 3
            },
            {
              "title": "pending item has Accept and Reject buttons",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 280,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10305,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:28:39.386Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--b088d-s-Accept-and-Reject-buttons-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--b088d-s-Accept-and-Reject-buttons-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 281,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10300,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:28:50.768Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--b088d-s-Accept-and-Reject-buttons-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--b088d-s-Accept-and-Reject-buttons-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-d5b20c5a038fdf23fa61",
              "file": "fundcustodian.spec.js",
              "line": 144,
              "column": 3
            },
            {
              "title": "admin balance grid shows all 4 custodians",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 282,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10377,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:29:02.837Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--2e485-grid-shows-all-4-custodians-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--2e485-grid-shows-all-4-custodians-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 283,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10267,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:29:14.235Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--2e485-grid-shows-all-4-custodians-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--2e485-grid-shows-all-4-custodians-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-574735f2d7aa737c4de4",
              "file": "fundcustodian.spec.js",
              "line": 149,
              "column": 3
            },
            {
              "title": "primary badge on Pramod tile",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 284,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10272,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:29:25.446Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--aa121-rimary-badge-on-Pramod-tile-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--aa121-rimary-badge-on-Pramod-tile-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 285,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10286,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:29:36.809Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--aa121-rimary-badge-on-Pramod-tile-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--aa121-rimary-badge-on-Pramod-tile-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-97cf0bf851e91b557836",
              "file": "fundcustodian.spec.js",
              "line": 159,
              "column": 3
            },
            {
              "title": "manage custodian button visible for admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 286,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10315,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:29:48.150Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--e42fc-an-button-visible-for-admin-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--e42fc-an-button-visible-for-admin-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 287,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10317,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:29:59.508Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--e42fc-an-button-visible-for-admin-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--e42fc-an-button-visible-for-admin-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-8b52981cd3eb5f5965b5",
              "file": "fundcustodian.spec.js",
              "line": 164,
              "column": 3
            },
            {
              "title": "admin sees all outstanding advances (not just own)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 288,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10351,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:30:11.145Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--9791e-ding-advances-not-just-own--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--9791e-ding-advances-not-just-own--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 289,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10342,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:30:22.821Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--9791e-ding-advances-not-just-own--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--9791e-ding-advances-not-just-own--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-a4bb90f7b065c8a0e369",
              "file": "fundcustodian.spec.js",
              "line": 168,
              "column": 3
            },
            {
              "title": "pending banner appears with count 2",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1283,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10302,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:27:41.417Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c5dfb-banner-appears-with-count-2-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c5dfb-banner-appears-with-count-2-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 1284,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10479,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:27:52.813Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c5dfb-banner-appears-with-count-2-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--c5dfb-banner-appears-with-count-2-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-5721b8f6fb5dd9cea365",
              "file": "fundcustodian.spec.js",
              "line": 131,
              "column": 3
            },
            {
              "title": "pending banner shows from-name and amount for both",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1285,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10400,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:28:04.867Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--f5e84-om-name-and-amount-for-both-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--f5e84-om-name-and-amount-for-both-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 1286,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10307,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:28:16.376Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--f5e84-om-name-and-amount-for-both-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--f5e84-om-name-and-amount-for-both-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-fe87a17783e6f6cbddeb",
              "file": "fundcustodian.spec.js",
              "line": 137,
              "column": 3
            },
            {
              "title": "pending item has Accept and Reject buttons",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1287,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10318,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:28:27.709Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--b088d-s-Accept-and-Reject-buttons-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--b088d-s-Accept-and-Reject-buttons-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 1288,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10293,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:28:39.100Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--b088d-s-Accept-and-Reject-buttons-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--b088d-s-Accept-and-Reject-buttons-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-7b266c8c9bc51287a120",
              "file": "fundcustodian.spec.js",
              "line": 144,
              "column": 3
            },
            {
              "title": "admin balance grid shows all 4 custodians",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1289,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10279,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:28:50.403Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--2e485-grid-shows-all-4-custodians-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--2e485-grid-shows-all-4-custodians-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 1290,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10353,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:29:02.180Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--2e485-grid-shows-all-4-custodians-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--2e485-grid-shows-all-4-custodians-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-1958f4c60f44583934fb",
              "file": "fundcustodian.spec.js",
              "line": 149,
              "column": 3
            },
            {
              "title": "primary badge on Pramod tile",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1291,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10303,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:29:13.575Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--aa121-rimary-badge-on-Pramod-tile-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--aa121-rimary-badge-on-Pramod-tile-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 1292,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:29:24.907Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--aa121-rimary-badge-on-Pramod-tile-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--aa121-rimary-badge-on-Pramod-tile-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-6fdaef92560b85a800dc",
              "file": "fundcustodian.spec.js",
              "line": 159,
              "column": 3
            },
            {
              "title": "manage custodian button visible for admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1293,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10276,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:29:36.168Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--e42fc-an-button-visible-for-admin-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--e42fc-an-button-visible-for-admin-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 1294,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10299,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:29:47.490Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--e42fc-an-button-visible-for-admin-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--e42fc-an-button-visible-for-admin-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-10340aa1573ae9c5ee45",
              "file": "fundcustodian.spec.js",
              "line": 164,
              "column": 3
            },
            {
              "title": "admin sees all outstanding advances (not just own)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1295,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10295,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:29:58.785Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--9791e-ding-advances-not-just-own--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--9791e-ding-advances-not-just-own--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 1296,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10330,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 128
                        },
                        "snippet": "  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 128
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  126 |     await page.goto(BASE_URL);\n  127 |     await handleModeSelector(page, 'overseer');\n> 128 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  129 |   });\n  130 |\n  131 |   test('pending banner appears with count 2', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:128:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:30:10.329Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--9791e-ding-advances-not-just-own--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--9791e-ding-advances-not-just-own--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-f7ddee31989f51c705ac",
              "file": "fundcustodian.spec.js",
              "line": 168,
              "column": 3
            }
          ]
        },
        {
          "title": "Dashboard — pramod (admin, primary custodian)",
          "file": "fundcustodian.spec.js",
          "line": 180,
          "column": 6,
          "specs": [
            {
              "title": "pramod balance card is negative and shows red text",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 290,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10303,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:185:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 185
                        },
                        "snippet": "  183 |     await page.goto(BASE_URL);\n  184 |     await handleModeSelector(page, 'custodian');\n> 185 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  186 |     const balEl = page.locator('#my-balance');\n  187 |     // Negative balance → .neg class\n  188 |     await expect(balEl).toHaveClass(/neg/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 185
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  183 |     await page.goto(BASE_URL);\n  184 |     await handleModeSelector(page, 'custodian');\n> 185 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  186 |     const balEl = page.locator('#my-balance');\n  187 |     // Negative balance → .neg class\n  188 |     await expect(balEl).toHaveClass(/neg/);\n    at /var/www/360lm/tests/fundcustodian.spec.js:185:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:30:34.314Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--4488f-negative-and-shows-red-text-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--4488f-negative-and-shows-red-text-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 185
                      }
                    },
                    {
                      "workerIndex": 291,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10283,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:185:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 185
                        },
                        "snippet": "  183 |     await page.goto(BASE_URL);\n  184 |     await handleModeSelector(page, 'custodian');\n> 185 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  186 |     const balEl = page.locator('#my-balance');\n  187 |     // Negative balance → .neg class\n  188 |     await expect(balEl).toHaveClass(/neg/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 185
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  183 |     await page.goto(BASE_URL);\n  184 |     await handleModeSelector(page, 'custodian');\n> 185 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  186 |     const balEl = page.locator('#my-balance');\n  187 |     // Negative balance → .neg class\n  188 |     await expect(balEl).toHaveClass(/neg/);\n    at /var/www/360lm/tests/fundcustodian.spec.js:185:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:30:45.800Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--4488f-negative-and-shows-red-text-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--4488f-negative-and-shows-red-text-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 185
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-b2cb37c30ab55afae59f",
              "file": "fundcustodian.spec.js",
              "line": 181,
              "column": 3
            },
            {
              "title": "pramod balance card is negative and shows red text",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1297,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10347,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:185:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 185
                        },
                        "snippet": "  183 |     await page.goto(BASE_URL);\n  184 |     await handleModeSelector(page, 'custodian');\n> 185 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  186 |     const balEl = page.locator('#my-balance');\n  187 |     // Negative balance → .neg class\n  188 |     await expect(balEl).toHaveClass(/neg/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 185
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  183 |     await page.goto(BASE_URL);\n  184 |     await handleModeSelector(page, 'custodian');\n> 185 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  186 |     const balEl = page.locator('#my-balance');\n  187 |     // Negative balance → .neg class\n  188 |     await expect(balEl).toHaveClass(/neg/);\n    at /var/www/360lm/tests/fundcustodian.spec.js:185:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:30:21.767Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--4488f-negative-and-shows-red-text-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--4488f-negative-and-shows-red-text-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 185
                      }
                    },
                    {
                      "workerIndex": 1298,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10304,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:185:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 185
                        },
                        "snippet": "  183 |     await page.goto(BASE_URL);\n  184 |     await handleModeSelector(page, 'custodian');\n> 185 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  186 |     const balEl = page.locator('#my-balance');\n  187 |     // Negative balance → .neg class\n  188 |     await expect(balEl).toHaveClass(/neg/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 185
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  183 |     await page.goto(BASE_URL);\n  184 |     await handleModeSelector(page, 'custodian');\n> 185 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  186 |     const balEl = page.locator('#my-balance');\n  187 |     // Negative balance → .neg class\n  188 |     await expect(balEl).toHaveClass(/neg/);\n    at /var/www/360lm/tests/fundcustodian.spec.js:185:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:30:33.149Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--4488f-negative-and-shows-red-text-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--4488f-negative-and-shows-red-text-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 185
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-a9e02f15a81841037820",
              "file": "fundcustodian.spec.js",
              "line": 181,
              "column": 3
            }
          ]
        },
        {
          "title": "Dashboard — gurpreet (new custodian, low activity)",
          "file": "fundcustodian.spec.js",
          "line": 197,
          "column": 6,
          "specs": [
            {
              "title": "gurpreet name shown in header",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 292,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10281,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:30:57.211Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--90af2-rpreet-name-shown-in-header-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--90af2-rpreet-name-shown-in-header-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    },
                    {
                      "workerIndex": 293,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10235,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:31:08.531Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--90af2-rpreet-name-shown-in-header-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--90af2-rpreet-name-shown-in-header-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-052bbbbb2ecdb12953d6",
              "file": "fundcustodian.spec.js",
              "line": 204,
              "column": 3
            },
            {
              "title": "positive balance shows .pos class",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 294,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10289,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:31:19.741Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--97d5c-ive-balance-shows-pos-class-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--97d5c-ive-balance-shows-pos-class-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    },
                    {
                      "workerIndex": 295,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10244,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:31:31.076Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--97d5c-ive-balance-shows-pos-class-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--97d5c-ive-balance-shows-pos-class-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-549ce3e00f5551bf942d",
              "file": "fundcustodian.spec.js",
              "line": 208,
              "column": 3
            },
            {
              "title": "no pending transfers for gurpreet",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 296,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10245,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:31:42.323Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--6fbdc-ding-transfers-for-gurpreet-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--6fbdc-ding-transfers-for-gurpreet-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    },
                    {
                      "workerIndex": 297,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10343,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:31:53.593Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--6fbdc-ding-transfers-for-gurpreet-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--6fbdc-ding-transfers-for-gurpreet-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-facad53c5aa6536b745e",
              "file": "fundcustodian.spec.js",
              "line": 213,
              "column": 3
            },
            {
              "title": "gurpreet name shown in header",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1299,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10244,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:30:44.463Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--90af2-rpreet-name-shown-in-header-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--90af2-rpreet-name-shown-in-header-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    },
                    {
                      "workerIndex": 1300,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10267,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:30:55.676Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--90af2-rpreet-name-shown-in-header-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--90af2-rpreet-name-shown-in-header-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-3c582c29aafe3d0e0ceb",
              "file": "fundcustodian.spec.js",
              "line": 204,
              "column": 3
            },
            {
              "title": "positive balance shows .pos class",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1301,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10321,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:31:07.065Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--97d5c-ive-balance-shows-pos-class-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--97d5c-ive-balance-shows-pos-class-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    },
                    {
                      "workerIndex": 1302,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10240,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:31:18.468Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--97d5c-ive-balance-shows-pos-class-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--97d5c-ive-balance-shows-pos-class-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-363c6279abff29f714bf",
              "file": "fundcustodian.spec.js",
              "line": 208,
              "column": 3
            },
            {
              "title": "no pending transfers for gurpreet",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1303,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10234,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:31:29.703Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--6fbdc-ding-transfers-for-gurpreet-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--6fbdc-ding-transfers-for-gurpreet-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    },
                    {
                      "workerIndex": 1304,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10225,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 201
                        },
                        "snippet": "  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  199 |     await injectSession(page, SESSION.gurpreet);\n  200 |     await page.goto(BASE_URL);\n> 201 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  202 |   });\n  203 |\n  204 |   test('gurpreet name shown in header', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:201:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:31:40.998Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--6fbdc-ding-transfers-for-gurpreet-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Dashboard-—--6fbdc-ding-transfers-for-gurpreet-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 201
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-e17a78787a3159fff6c8",
              "file": "fundcustodian.spec.js",
              "line": 213,
              "column": 3
            }
          ]
        },
        {
          "title": "Transfer Form",
          "file": "fundcustodian.spec.js",
          "line": 221,
          "column": 6,
          "specs": [
            {
              "title": "giver mode selected by default",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 298,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30068,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:32:05.087Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-giver-mode-selected-by-default-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-giver-mode-selected-by-default-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 299,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30109,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:32:36.170Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-giver-mode-selected-by-default-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-giver-mode-selected-by-default-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-ef68eb6005a875505ba7",
              "file": "fundcustodian.spec.js",
              "line": 229,
              "column": 3
            },
            {
              "title": "cash + giver mode shows pending info notice",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 300,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30070,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:33:07.375Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-48c45-e-shows-pending-info-notice-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-48c45-e-shows-pending-info-notice-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 301,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:33:38.494Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-48c45-e-shows-pending-info-notice-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-48c45-e-shows-pending-info-notice-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-3b24fd4ba134e6a26c56",
              "file": "fundcustodian.spec.js",
              "line": 234,
              "column": 3
            },
            {
              "title": "switching to receiver mode hides pending notice",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 302,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30076,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:34:09.685Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-4933a-r-mode-hides-pending-notice-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-4933a-r-mode-hides-pending-notice-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 303,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30088,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:34:40.901Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-4933a-r-mode-hides-pending-notice-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-4933a-r-mode-hides-pending-notice-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-48b5d9e149b2131e7da0",
              "file": "fundcustodian.spec.js",
              "line": 238,
              "column": 3
            },
            {
              "title": "cash mode selected by default, ref field hidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 304,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:35:12.070Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-c78c7-by-default-ref-field-hidden-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-c78c7-by-default-ref-field-hidden-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 305,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30089,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:35:43.145Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-c78c7-by-default-ref-field-hidden-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-c78c7-by-default-ref-field-hidden-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-459c826d3e9306798995",
              "file": "fundcustodian.spec.js",
              "line": 243,
              "column": 3
            },
            {
              "title": "selecting UPI shows reference field",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 306,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30085,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:36:14.261Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-ef5f4-g-UPI-shows-reference-field-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-ef5f4-g-UPI-shows-reference-field-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 307,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30075,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:36:45.301Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-ef5f4-g-UPI-shows-reference-field-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-ef5f4-g-UPI-shows-reference-field-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-9418cb203efc2b9176eb",
              "file": "fundcustodian.spec.js",
              "line": 248,
              "column": 3
            },
            {
              "title": "selecting NEFT shows reference field",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 308,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:37:16.361Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-5d0bc--NEFT-shows-reference-field-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-5d0bc--NEFT-shows-reference-field-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 309,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:37:47.581Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-5d0bc--NEFT-shows-reference-field-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-5d0bc--NEFT-shows-reference-field-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-fcc7cc3ec4a43af56054",
              "file": "fundcustodian.spec.js",
              "line": 253,
              "column": 3
            },
            {
              "title": "selecting IMPS shows reference field",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 310,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:38:18.786Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-3b50d--IMPS-shows-reference-field-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-3b50d--IMPS-shows-reference-field-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 311,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30104,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:38:49.951Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-3b50d--IMPS-shows-reference-field-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-3b50d--IMPS-shows-reference-field-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-e467bd64164b4bdca24c",
              "file": "fundcustodian.spec.js",
              "line": 258,
              "column": 3
            },
            {
              "title": "selecting Cheque shows reference field",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 312,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30086,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:39:21.119Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-2e986-heque-shows-reference-field-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-2e986-heque-shows-reference-field-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 313,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30080,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:39:52.325Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-2e986-heque-shows-reference-field-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-2e986-heque-shows-reference-field-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-961abc88669b93f590d6",
              "file": "fundcustodian.spec.js",
              "line": 263,
              "column": 3
            },
            {
              "title": "selecting RTGS shows reference field",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 314,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30066,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:40:23.535Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8f503--RTGS-shows-reference-field-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8f503--RTGS-shows-reference-field-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 315,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:40:54.578Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8f503--RTGS-shows-reference-field-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8f503--RTGS-shows-reference-field-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-f391141f12c24c94fc40",
              "file": "fundcustodian.spec.js",
              "line": 268,
              "column": 3
            },
            {
              "title": "UPI mode: ref label is UPI Ref No., UPI Pay button visible",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 316,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30086,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:41:25.666Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-09740-f-No-UPI-Pay-button-visible-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-09740-f-No-UPI-Pay-button-visible-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 317,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30092,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:41:56.822Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-09740-f-No-UPI-Pay-button-visible-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-09740-f-No-UPI-Pay-button-visible-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-482414d69303f6280cfe",
              "file": "fundcustodian.spec.js",
              "line": 273,
              "column": 3
            },
            {
              "title": "NEFT mode: ref label is UTR No., UPI Pay button hidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 318,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30112,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:42:28.087Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-a3c7a-TR-No-UPI-Pay-button-hidden-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-a3c7a-TR-No-UPI-Pay-button-hidden-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 319,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30094,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:42:59.295Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-a3c7a-TR-No-UPI-Pay-button-hidden-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-a3c7a-TR-No-UPI-Pay-button-hidden-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-22c486a234a865f79dab",
              "file": "fundcustodian.spec.js",
              "line": 279,
              "column": 3
            },
            {
              "title": "Cheque mode: shows bank and branch fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 320,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30104,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:43:30.437Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-93153-hows-bank-and-branch-fields-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-93153-hows-bank-and-branch-fields-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 321,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:44:02.102Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-93153-hows-bank-and-branch-fields-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-93153-hows-bank-and-branch-fields-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-a682193f009529ce5799",
              "file": "fundcustodian.spec.js",
              "line": 285,
              "column": 3
            },
            {
              "title": "Cash mode: cheque extra fields hidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 322,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30138,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:44:33.514Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-cee33--cheque-extra-fields-hidden-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-cee33--cheque-extra-fields-hidden-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 323,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:45:05.061Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-cee33--cheque-extra-fields-hidden-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-cee33--cheque-extra-fields-hidden-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-cc20986b6a995ac55c48",
              "file": "fundcustodian.spec.js",
              "line": 292,
              "column": 3
            },
            {
              "title": "returning to cash hides reference field again",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 324,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:45:36.310Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8fd93-hides-reference-field-again-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8fd93-hides-reference-field-again-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 325,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30083,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:46:07.416Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8fd93-hides-reference-field-again-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8fd93-hides-reference-field-again-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-58b9ed70f79484eba41d",
              "file": "fundcustodian.spec.js",
              "line": 298,
              "column": 3
            },
            {
              "title": "custodian select excludes self (shambhu not in list)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 326,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:46:38.719Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-41264-s-self-shambhu-not-in-list--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-41264-s-self-shambhu-not-in-list--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 327,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30094,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:47:09.827Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-41264-s-self-shambhu-not-in-list--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-41264-s-self-shambhu-not-in-list--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-551ecf079ee570d8d027",
              "file": "fundcustodian.spec.js",
              "line": 305,
              "column": 3
            },
            {
              "title": "submit with no custodian shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 328,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30097,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:47:41.122Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-b5887-custodian-shows-error-toast-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-b5887-custodian-shows-error-toast-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 329,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30087,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:48:12.416Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-b5887-custodian-shows-error-toast-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-b5887-custodian-shows-error-toast-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-f972700ca1e442981b79",
              "file": "fundcustodian.spec.js",
              "line": 313,
              "column": 3
            },
            {
              "title": "submit with no amount shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 330,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30089,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:48:43.564Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-78c48-no-amount-shows-error-toast-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-78c48-no-amount-shows-error-toast-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 331,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:49:14.694Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-78c48-no-amount-shows-error-toast-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-78c48-no-amount-shows-error-toast-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-c66e845362f4505f2ccb",
              "file": "fundcustodian.spec.js",
              "line": 319,
              "column": 3
            },
            {
              "title": "today date is pre-filled",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 332,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30085,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:49:45.797Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-today-date-is-pre-filled-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-today-date-is-pre-filled-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 333,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30073,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:50:17.006Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-today-date-is-pre-filled-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-today-date-is-pre-filled-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-3a49838b38cadbcae924",
              "file": "fundcustodian.spec.js",
              "line": 325,
              "column": 3
            },
            {
              "title": "clear button resets form to defaults",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 334,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30071,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:50:48.216Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-eff59-ton-resets-form-to-defaults-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-eff59-ton-resets-form-to-defaults-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 335,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30076,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:51:19.380Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-eff59-ton-resets-form-to-defaults-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-eff59-ton-resets-form-to-defaults-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-1a92bf0cbe124ff4a8ef",
              "file": "fundcustodian.spec.js",
              "line": 330,
              "column": 3
            },
            {
              "title": "giver mode selected by default",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1305,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:31:52.277Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-giver-mode-selected-by-default-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-giver-mode-selected-by-default-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1306,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30064,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:32:23.414Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-giver-mode-selected-by-default-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-giver-mode-selected-by-default-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-1125987eeee3827488db",
              "file": "fundcustodian.spec.js",
              "line": 229,
              "column": 3
            },
            {
              "title": "cash + giver mode shows pending info notice",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1307,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30071,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:32:54.622Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-48c45-e-shows-pending-info-notice-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-48c45-e-shows-pending-info-notice-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1308,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:33:25.641Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-48c45-e-shows-pending-info-notice-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-48c45-e-shows-pending-info-notice-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-a3f297cc0c13547b65d2",
              "file": "fundcustodian.spec.js",
              "line": 234,
              "column": 3
            },
            {
              "title": "switching to receiver mode hides pending notice",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1309,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30063,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:33:56.722Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-4933a-r-mode-hides-pending-notice-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-4933a-r-mode-hides-pending-notice-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1310,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30091,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:34:27.774Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-4933a-r-mode-hides-pending-notice-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-4933a-r-mode-hides-pending-notice-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-d34ad27dbae4556b0e77",
              "file": "fundcustodian.spec.js",
              "line": 238,
              "column": 3
            },
            {
              "title": "cash mode selected by default, ref field hidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1311,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30069,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:34:58.855Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-c78c7-by-default-ref-field-hidden-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-c78c7-by-default-ref-field-hidden-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1312,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30101,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:35:29.942Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-c78c7-by-default-ref-field-hidden-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-c78c7-by-default-ref-field-hidden-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-d65813e414edac672519",
              "file": "fundcustodian.spec.js",
              "line": 243,
              "column": 3
            },
            {
              "title": "selecting UPI shows reference field",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1313,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30065,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:36:01.258Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-ef5f4-g-UPI-shows-reference-field-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-ef5f4-g-UPI-shows-reference-field-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1314,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30139,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:36:32.476Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-ef5f4-g-UPI-shows-reference-field-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-ef5f4-g-UPI-shows-reference-field-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-cbb8e990279bafb125c2",
              "file": "fundcustodian.spec.js",
              "line": 248,
              "column": 3
            },
            {
              "title": "selecting NEFT shows reference field",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1315,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:37:03.898Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-5d0bc--NEFT-shows-reference-field-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-5d0bc--NEFT-shows-reference-field-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1316,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:37:34.995Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-5d0bc--NEFT-shows-reference-field-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-5d0bc--NEFT-shows-reference-field-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-d8f923b25c45290b6ecc",
              "file": "fundcustodian.spec.js",
              "line": 253,
              "column": 3
            },
            {
              "title": "selecting IMPS shows reference field",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1317,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30086,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:38:06.142Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-3b50d--IMPS-shows-reference-field-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-3b50d--IMPS-shows-reference-field-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1318,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30134,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:38:37.233Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-3b50d--IMPS-shows-reference-field-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-3b50d--IMPS-shows-reference-field-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-4bccbf499a57974c4ef9",
              "file": "fundcustodian.spec.js",
              "line": 258,
              "column": 3
            },
            {
              "title": "selecting Cheque shows reference field",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1319,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30085,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:39:08.522Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-2e986-heque-shows-reference-field-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-2e986-heque-shows-reference-field-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1320,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30088,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:39:39.700Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-2e986-heque-shows-reference-field-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-2e986-heque-shows-reference-field-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-764f1e9ac54001c4e94d",
              "file": "fundcustodian.spec.js",
              "line": 263,
              "column": 3
            },
            {
              "title": "selecting RTGS shows reference field",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1321,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30076,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:40:10.962Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8f503--RTGS-shows-reference-field-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8f503--RTGS-shows-reference-field-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1322,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30070,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:40:42.085Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8f503--RTGS-shows-reference-field-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8f503--RTGS-shows-reference-field-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-0af5069bd0d80fa6b390",
              "file": "fundcustodian.spec.js",
              "line": 268,
              "column": 3
            },
            {
              "title": "UPI mode: ref label is UPI Ref No., UPI Pay button visible",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1323,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:41:13.146Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-09740-f-No-UPI-Pay-button-visible-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-09740-f-No-UPI-Pay-button-visible-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1324,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:41:44.271Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-09740-f-No-UPI-Pay-button-visible-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-09740-f-No-UPI-Pay-button-visible-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-d571b3f46e661c5ed8b6",
              "file": "fundcustodian.spec.js",
              "line": 273,
              "column": 3
            },
            {
              "title": "NEFT mode: ref label is UTR No., UPI Pay button hidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1325,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30099,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:42:15.401Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-a3c7a-TR-No-UPI-Pay-button-hidden-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-a3c7a-TR-No-UPI-Pay-button-hidden-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1326,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30069,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:42:46.598Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-a3c7a-TR-No-UPI-Pay-button-hidden-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-a3c7a-TR-No-UPI-Pay-button-hidden-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-1040e06c8ca7dd5e9901",
              "file": "fundcustodian.spec.js",
              "line": 279,
              "column": 3
            },
            {
              "title": "Cheque mode: shows bank and branch fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1327,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30070,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:43:17.694Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-93153-hows-bank-and-branch-fields-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-93153-hows-bank-and-branch-fields-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1328,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30088,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:43:48.754Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-93153-hows-bank-and-branch-fields-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-93153-hows-bank-and-branch-fields-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-320f3225dbc1800baa4f",
              "file": "fundcustodian.spec.js",
              "line": 285,
              "column": 3
            },
            {
              "title": "Cash mode: cheque extra fields hidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1329,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:44:19.822Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-cee33--cheque-extra-fields-hidden-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-cee33--cheque-extra-fields-hidden-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1330,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30092,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:44:50.962Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-cee33--cheque-extra-fields-hidden-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-cee33--cheque-extra-fields-hidden-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-d530f230e884b8e5472b",
              "file": "fundcustodian.spec.js",
              "line": 292,
              "column": 3
            },
            {
              "title": "returning to cash hides reference field again",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1331,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:45:22.287Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8fd93-hides-reference-field-again-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8fd93-hides-reference-field-again-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1332,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:45:53.416Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8fd93-hides-reference-field-again-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-8fd93-hides-reference-field-again-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-77292d8be87cc2fca380",
              "file": "fundcustodian.spec.js",
              "line": 298,
              "column": 3
            },
            {
              "title": "custodian select excludes self (shambhu not in list)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1333,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30061,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:46:24.500Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-41264-s-self-shambhu-not-in-list--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-41264-s-self-shambhu-not-in-list--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1334,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:46:55.577Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-41264-s-self-shambhu-not-in-list--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-41264-s-self-shambhu-not-in-list--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-795c63d872974f0b0996",
              "file": "fundcustodian.spec.js",
              "line": 305,
              "column": 3
            },
            {
              "title": "submit with no custodian shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1335,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30087,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:47:26.679Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-b5887-custodian-shows-error-toast-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-b5887-custodian-shows-error-toast-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1336,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30076,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:47:57.825Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-b5887-custodian-shows-error-toast-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-b5887-custodian-shows-error-toast-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-93553a68f921a13a1ff6",
              "file": "fundcustodian.spec.js",
              "line": 313,
              "column": 3
            },
            {
              "title": "submit with no amount shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1337,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30075,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:48:28.966Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-78c48-no-amount-shows-error-toast-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-78c48-no-amount-shows-error-toast-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1338,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:49:00.122Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-78c48-no-amount-shows-error-toast-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-78c48-no-amount-shows-error-toast-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-c584ed83567c4d0e42a7",
              "file": "fundcustodian.spec.js",
              "line": 319,
              "column": 3
            },
            {
              "title": "today date is pre-filled",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1339,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30221,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:49:31.249Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-today-date-is-pre-filled-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-today-date-is-pre-filled-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1340,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30068,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:50:03.332Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-today-date-is-pre-filled-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Form-today-date-is-pre-filled-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-be22f6a44ee11b2e5370",
              "file": "fundcustodian.spec.js",
              "line": 325,
              "column": 3
            },
            {
              "title": "clear button resets form to defaults",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1341,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:50:34.531Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-eff59-ton-resets-form-to-defaults-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-eff59-ton-resets-form-to-defaults-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    },
                    {
                      "workerIndex": 1342,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30066,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 222
                        },
                        "snippet": "  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 222
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  220 | // ─────────────────────────────────────────────────────────────────\n  221 | test.describe('Transfer Form', () => {\n> 222 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n  225 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:222:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 225
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  223 |     await injectSession(page, SESSION.shambhu);\n  224 |     await page.goto(BASE_URL);\n> 225 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  226 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  227 |   });\n  228 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:225:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:51:05.742Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-eff59-ton-resets-form-to-defaults-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-For-eff59-ton-resets-form-to-defaults-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 222
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-980ba8254336a5f5fb57",
              "file": "fundcustodian.spec.js",
              "line": 330,
              "column": 3
            }
          ]
        },
        {
          "title": "Transfer Categories",
          "file": "fundcustodian.spec.js",
          "line": 348,
          "column": 6,
          "specs": [
            {
              "title": "four category chips present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 336,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30087,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:51:50.478Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-10497-four-category-chips-present-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-10497-four-category-chips-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 337,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30068,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:52:21.654Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-10497-four-category-chips-present-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-10497-four-category-chips-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-8aaa0e46d9eb603f349b",
              "file": "fundcustodian.spec.js",
              "line": 356,
              "column": 3
            },
            {
              "title": "Custodian category (default) shows custodian dropdown, hides people search",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 338,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30099,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:52:52.796Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-9f4c1-ropdown-hides-people-search-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-9f4c1-ropdown-hides-people-search-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 339,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:53:23.931Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-9f4c1-ropdown-hides-people-search-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-9f4c1-ropdown-hides-people-search-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-0fd1ddac5b10786ec357",
              "file": "fundcustodian.spec.js",
              "line": 361,
              "column": 3
            },
            {
              "title": "Employee category shows people search + advance type (giving)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 340,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30076,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:53:55.048Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-f099b-search-advance-type-giving--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-f099b-search-advance-type-giving--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 341,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30070,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:54:26.204Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-f099b-search-advance-type-giving--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-f099b-search-advance-type-giving--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-29e963e7ccc7689595e1",
              "file": "fundcustodian.spec.js",
              "line": 367,
              "column": 3
            },
            {
              "title": "Party category shows people search, no advance type",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 342,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30080,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:54:57.174Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-34d5f-ople-search-no-advance-type-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-34d5f-ople-search-no-advance-type-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 343,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30084,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:55:28.278Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-34d5f-ople-search-no-advance-type-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-34d5f-ople-search-no-advance-type-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-3038419719dbc83ff7c6",
              "file": "fundcustodian.spec.js",
              "line": 374,
              "column": 3
            },
            {
              "title": "Receiving + Employee shows receipt purpose, hides advance type",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 344,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30096,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:55:59.470Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-ca7be--purpose-hides-advance-type-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-ca7be--purpose-hides-advance-type-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 345,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30081,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:56:30.594Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-ca7be--purpose-hides-advance-type-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-ca7be--purpose-hides-advance-type-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-fca176e6e110f99c09db",
              "file": "fundcustodian.spec.js",
              "line": 380,
              "column": 3
            },
            {
              "title": "people search returns results (live search)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 346,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:57:01.865Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-0ea53-eturns-results-live-search--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-0ea53-eturns-results-live-search--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 347,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30135,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:57:33.135Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-0ea53-eturns-results-live-search--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-0ea53-eturns-results-live-search--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-0c6cf4769cd92506ab29",
              "file": "fundcustodian.spec.js",
              "line": 387,
              "column": 3
            },
            {
              "title": "selecting a searched payee shows selection card",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 348,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30069,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:58:04.683Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-03c18--payee-shows-selection-card-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-03c18--payee-shows-selection-card-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 349,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30096,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:58:35.746Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-03c18--payee-shows-selection-card-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-03c18--payee-shows-selection-card-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-ad3d81dc568bae5f6667",
              "file": "fundcustodian.spec.js",
              "line": 393,
              "column": 3
            },
            {
              "title": "alias search finds Shambhu via \"shambu\" spelling",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 350,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T19:59:06.845Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b54f7-Shambhu-via-shambu-spelling-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b54f7-Shambhu-via-shambu-spelling-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 351,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30207,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T19:59:37.901Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b54f7-Shambhu-via-shambu-spelling-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b54f7-Shambhu-via-shambu-spelling-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-aab7e5bff8ca3227747e",
              "file": "fundcustodian.spec.js",
              "line": 401,
              "column": 3
            },
            {
              "title": "no-result search shows no-match message and add button for all users",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 352,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30111,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:00:09.715Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-8f3c7-nd-add-button-for-all-users-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-8f3c7-nd-add-button-for-all-users-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 353,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30089,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:00:41.092Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-8f3c7-nd-add-button-for-all-users-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-8f3c7-nd-add-button-for-all-users-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-b03a5f0ff15e6e675823",
              "file": "fundcustodian.spec.js",
              "line": 410,
              "column": 3
            },
            {
              "title": "UPI App button only for custodian category",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 354,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:01:12.267Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-39799-only-for-custodian-category-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-39799-only-for-custodian-category-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 355,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30094,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:01:43.366Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-39799-only-for-custodian-category-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-39799-only-for-custodian-category-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-33438aebca46eacea8da",
              "file": "fundcustodian.spec.js",
              "line": 420,
              "column": 3
            },
            {
              "title": "submit employee transfer with no advance type shows error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 356,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30092,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:02:14.493Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b0833-no-advance-type-shows-error-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b0833-no-advance-type-shows-error-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 357,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30066,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:02:45.565Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b0833-no-advance-type-shows-error-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b0833-no-advance-type-shows-error-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-8bdf5ae3b31046da9e69",
              "file": "fundcustodian.spec.js",
              "line": 429,
              "column": 3
            },
            {
              "title": "four category chips present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1343,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30081,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:51:36.917Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-10497-four-category-chips-present-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-10497-four-category-chips-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 1344,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:52:08.227Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-10497-four-category-chips-present-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-10497-four-category-chips-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-9c34d9e686aa60096aa5",
              "file": "fundcustodian.spec.js",
              "line": 356,
              "column": 3
            },
            {
              "title": "Custodian category (default) shows custodian dropdown, hides people search",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1345,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30067,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:52:39.437Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-9f4c1-ropdown-hides-people-search-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-9f4c1-ropdown-hides-people-search-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 1346,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:53:10.673Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-9f4c1-ropdown-hides-people-search-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-9f4c1-ropdown-hides-people-search-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-659c3ea7f705efc94fdf",
              "file": "fundcustodian.spec.js",
              "line": 361,
              "column": 3
            },
            {
              "title": "Employee category shows people search + advance type (giving)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1347,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30070,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:53:41.779Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-f099b-search-advance-type-giving--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-f099b-search-advance-type-giving--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 1348,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30091,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:54:12.823Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-f099b-search-advance-type-giving--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-f099b-search-advance-type-giving--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-81ac50e083fbda946b9f",
              "file": "fundcustodian.spec.js",
              "line": 367,
              "column": 3
            },
            {
              "title": "Party category shows people search, no advance type",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1349,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30081,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:54:43.875Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-34d5f-ople-search-no-advance-type-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-34d5f-ople-search-no-advance-type-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 1350,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:55:15.018Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-34d5f-ople-search-no-advance-type-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-34d5f-ople-search-no-advance-type-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-7e2dbf289eb090f0b508",
              "file": "fundcustodian.spec.js",
              "line": 374,
              "column": 3
            },
            {
              "title": "Receiving + Employee shows receipt purpose, hides advance type",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1351,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30065,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:55:46.039Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-ca7be--purpose-hides-advance-type-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-ca7be--purpose-hides-advance-type-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 1352,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30086,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:56:17.089Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-ca7be--purpose-hides-advance-type-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-ca7be--purpose-hides-advance-type-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-25060a72b786deb65749",
              "file": "fundcustodian.spec.js",
              "line": 380,
              "column": 3
            },
            {
              "title": "people search returns results (live search)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1353,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30081,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:56:48.155Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-0ea53-eturns-results-live-search--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-0ea53-eturns-results-live-search--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 1354,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:57:19.318Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-0ea53-eturns-results-live-search--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-0ea53-eturns-results-live-search--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-bdac2e31ec6f815e2d6c",
              "file": "fundcustodian.spec.js",
              "line": 387,
              "column": 3
            },
            {
              "title": "selecting a searched payee shows selection card",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1355,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:57:50.474Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-03c18--payee-shows-selection-card-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-03c18--payee-shows-selection-card-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 1356,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30075,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:58:21.627Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-03c18--payee-shows-selection-card-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-03c18--payee-shows-selection-card-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-e8b5e8975c397b946cb8",
              "file": "fundcustodian.spec.js",
              "line": 393,
              "column": 3
            },
            {
              "title": "alias search finds Shambhu via \"shambu\" spelling",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1357,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:58:52.819Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b54f7-Shambhu-via-shambu-spelling-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b54f7-Shambhu-via-shambu-spelling-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 1358,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30070,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T22:59:23.938Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b54f7-Shambhu-via-shambu-spelling-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b54f7-Shambhu-via-shambu-spelling-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-7c42a5237478ecec877e",
              "file": "fundcustodian.spec.js",
              "line": 401,
              "column": 3
            },
            {
              "title": "no-result search shows no-match message and add button for all users",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1359,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30102,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T22:59:55.068Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-8f3c7-nd-add-button-for-all-users-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-8f3c7-nd-add-button-for-all-users-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 1360,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30087,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:00:26.374Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-8f3c7-nd-add-button-for-all-users-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-8f3c7-nd-add-button-for-all-users-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-1a2f249b279fa49b67a7",
              "file": "fundcustodian.spec.js",
              "line": 410,
              "column": 3
            },
            {
              "title": "UPI App button only for custodian category",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1361,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30110,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:00:57.642Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-39799-only-for-custodian-category-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-39799-only-for-custodian-category-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 1362,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:01:28.893Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-39799-only-for-custodian-category-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-39799-only-for-custodian-category-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-f0f9c235f06238c77d04",
              "file": "fundcustodian.spec.js",
              "line": 420,
              "column": 3
            },
            {
              "title": "submit employee transfer with no advance type shows error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1363,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:02:00.052Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b0833-no-advance-type-shows-error-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b0833-no-advance-type-shows-error-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    },
                    {
                      "workerIndex": 1364,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30176,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 349
                        },
                        "snippet": "  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 349
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  347 | // ─────────────────────────────────────────────────────────────────\n  348 | test.describe('Transfer Categories', () => {\n> 349 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n  352 |     await page.locator('#nav-transfer').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:349:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 352
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  350 |     await injectSession(page, SESSION.shambhu);\n  351 |     await page.goto(BASE_URL);\n> 352 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  353 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/);\n  354 |   });\n  355 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:352:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:02:31.258Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b0833-no-advance-type-shows-error-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Transfer-Cat-b0833-no-advance-type-shows-error-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 349
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-f734bba961ddb477556b",
              "file": "fundcustodian.spec.js",
              "line": 429,
              "column": 3
            }
          ]
        },
        {
          "title": "Ledger — Transfers tab",
          "file": "fundcustodian.spec.js",
          "line": 443,
          "column": 6,
          "specs": [
            {
              "title": "All tab active by default when ledger opens",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 358,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10262,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:03:16.719Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-84e14-y-default-when-ledger-opens-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-84e14-y-default-when-ledger-opens-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    },
                    {
                      "workerIndex": 359,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10248,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:03:27.955Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-84e14-y-default-when-ledger-opens-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-84e14-y-default-when-ledger-opens-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-e5343b19494bd40df43e",
              "file": "fundcustodian.spec.js",
              "line": 452,
              "column": 3
            },
            {
              "title": "shambhu ledger shows transfer rows after clicking Transfers tab",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 360,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10248,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:03:39.157Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-8d0d8-fter-clicking-Transfers-tab-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-8d0d8-fter-clicking-Transfers-tab-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    },
                    {
                      "workerIndex": 361,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10256,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:03:50.427Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-8d0d8-fter-clicking-Transfers-tab-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-8d0d8-fter-clicking-Transfers-tab-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-517c577a5d473b83979b",
              "file": "fundcustodian.spec.js",
              "line": 459,
              "column": 3
            },
            {
              "title": "status badges present in transfer rows",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 362,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10284,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:04:02.501Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-4b0cb-es-present-in-transfer-rows-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-4b0cb-es-present-in-transfer-rows-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    },
                    {
                      "workerIndex": 363,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:04:13.879Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-4b0cb-es-present-in-transfer-rows-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-4b0cb-es-present-in-transfer-rows-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-6c71a1e8e23d5545818f",
              "file": "fundcustodian.spec.js",
              "line": 466,
              "column": 3
            },
            {
              "title": "switching to Advances tab shows advances panel",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 364,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10249,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:04:25.096Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-320e8-es-tab-shows-advances-panel-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-320e8-es-tab-shows-advances-panel-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    },
                    {
                      "workerIndex": 365,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10248,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:04:36.341Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-320e8-es-tab-shows-advances-panel-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-320e8-es-tab-shows-advances-panel-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-eaf76ec5b56aa7863fd6",
              "file": "fundcustodian.spec.js",
              "line": 471,
              "column": 3
            },
            {
              "title": "no custodian filter for non-admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 366,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10278,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:04:47.674Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-d5783-todian-filter-for-non-admin-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-d5783-todian-filter-for-non-admin-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    },
                    {
                      "workerIndex": 367,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10266,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:04:59.019Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-d5783-todian-filter-for-non-admin-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-d5783-todian-filter-for-non-admin-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-e50e79ea7c8995f141a7",
              "file": "fundcustodian.spec.js",
              "line": 478,
              "column": 3
            },
            {
              "title": "All tab active by default when ledger opens",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1365,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10333,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:03:03.000Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-84e14-y-default-when-ledger-opens-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-84e14-y-default-when-ledger-opens-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    },
                    {
                      "workerIndex": 1366,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10257,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:03:14.347Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-84e14-y-default-when-ledger-opens-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-84e14-y-default-when-ledger-opens-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-c0f6547bd78771a098bd",
              "file": "fundcustodian.spec.js",
              "line": 452,
              "column": 3
            },
            {
              "title": "shambhu ledger shows transfer rows after clicking Transfers tab",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1367,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10237,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:03:25.577Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-8d0d8-fter-clicking-Transfers-tab-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-8d0d8-fter-clicking-Transfers-tab-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    },
                    {
                      "workerIndex": 1368,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10227,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:03:36.845Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-8d0d8-fter-clicking-Transfers-tab-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-8d0d8-fter-clicking-Transfers-tab-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-e51ebb7b5461810e2612",
              "file": "fundcustodian.spec.js",
              "line": 459,
              "column": 3
            },
            {
              "title": "status badges present in transfer rows",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1369,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10257,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:03:48.072Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-4b0cb-es-present-in-transfer-rows-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-4b0cb-es-present-in-transfer-rows-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    },
                    {
                      "workerIndex": 1370,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10261,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:03:59.378Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-4b0cb-es-present-in-transfer-rows-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-4b0cb-es-present-in-transfer-rows-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-335a7b8c0c60f9928824",
              "file": "fundcustodian.spec.js",
              "line": 466,
              "column": 3
            },
            {
              "title": "switching to Advances tab shows advances panel",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1371,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10244,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:04:10.661Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-320e8-es-tab-shows-advances-panel-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-320e8-es-tab-shows-advances-panel-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    },
                    {
                      "workerIndex": 1372,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10260,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:04:21.832Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-320e8-es-tab-shows-advances-panel-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-320e8-es-tab-shows-advances-panel-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-2d71cb941d942a192eac",
              "file": "fundcustodian.spec.js",
              "line": 471,
              "column": 3
            },
            {
              "title": "no custodian filter for non-admin",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1373,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10217,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:04:33.046Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-d5783-todian-filter-for-non-admin-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-d5783-todian-filter-for-non-admin-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    },
                    {
                      "workerIndex": 1374,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10247,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 447
                        },
                        "snippet": "  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 447
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  445 |     await injectSession(page, SESSION.shambhu);\n  446 |     await page.goto(BASE_URL);\n> 447 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  448 |     await page.locator('#nav-ledger').click();\n  449 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/);\n  450 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:447:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:04:44.260Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-d5783-todian-filter-for-non-admin-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Tra-d5783-todian-filter-for-non-admin-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 447
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-07916bfb4aab00ca5be7",
              "file": "fundcustodian.spec.js",
              "line": 478,
              "column": 3
            }
          ]
        },
        {
          "title": "Ledger — admin (harish) with custodian filter",
          "file": "fundcustodian.spec.js",
          "line": 486,
          "column": 6,
          "specs": [
            {
              "title": "admin sees custodian filter dropdown",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 368,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30081,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 491
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  489 |     await page.goto(BASE_URL);\n  490 |     await handleModeSelector(page, 'overseer');\n> 491 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  492 |     await expect(page.locator('#ledger-cust-filter')).toBeVisible({ timeout: 8000 });\n  493 |     // Filter should have all custodians\n  494 |     const options = await page.locator('#ledger-cust-sel option').allTextContents();\n    at /var/www/360lm/tests/fundcustodian.spec.js:491:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:05:10.320Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-adm-7d71b-s-custodian-filter-dropdown-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-adm-7d71b-s-custodian-filter-dropdown-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 369,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 491
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  489 |     await page.goto(BASE_URL);\n  490 |     await handleModeSelector(page, 'overseer');\n> 491 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  492 |     await expect(page.locator('#ledger-cust-filter')).toBeVisible({ timeout: 8000 });\n  493 |     // Filter should have all custodians\n  494 |     const options = await page.locator('#ledger-cust-sel option').allTextContents();\n    at /var/www/360lm/tests/fundcustodian.spec.js:491:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:05:41.498Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-adm-7d71b-s-custodian-filter-dropdown-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-adm-7d71b-s-custodian-filter-dropdown-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-a59d920e2132ee7fefea",
              "file": "fundcustodian.spec.js",
              "line": 487,
              "column": 3
            },
            {
              "title": "admin sees custodian filter dropdown",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1375,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30087,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 491
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  489 |     await page.goto(BASE_URL);\n  490 |     await handleModeSelector(page, 'overseer');\n> 491 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  492 |     await expect(page.locator('#ledger-cust-filter')).toBeVisible({ timeout: 8000 });\n  493 |     // Filter should have all custodians\n  494 |     const options = await page.locator('#ledger-cust-sel option').allTextContents();\n    at /var/www/360lm/tests/fundcustodian.spec.js:491:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:04:55.555Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-adm-7d71b-s-custodian-filter-dropdown-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-adm-7d71b-s-custodian-filter-dropdown-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1376,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30113,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 491
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  489 |     await page.goto(BASE_URL);\n  490 |     await handleModeSelector(page, 'overseer');\n> 491 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  492 |     await expect(page.locator('#ledger-cust-filter')).toBeVisible({ timeout: 8000 });\n  493 |     // Filter should have all custodians\n  494 |     const options = await page.locator('#ledger-cust-sel option').allTextContents();\n    at /var/www/360lm/tests/fundcustodian.spec.js:491:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:05:26.630Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-adm-7d71b-s-custodian-filter-dropdown-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-adm-7d71b-s-custodian-filter-dropdown-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-c12a16483317dcc85e8c",
              "file": "fundcustodian.spec.js",
              "line": 487,
              "column": 3
            }
          ]
        },
        {
          "title": "Ledger — Advances tab data",
          "file": "fundcustodian.spec.js",
          "line": 503,
          "column": 6,
          "specs": [
            {
              "title": "advances show status badges (outstanding / partially_settled / settled)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 370,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30088,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 508
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  506 |     await page.goto(BASE_URL);\n  507 |     await handleModeSelector(page, 'overseer');\n> 508 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  509 |     await page.locator('#tab-advances').click();\n  510 |     await expect(page.locator('#advances-list .ledger-row').first()).toBeVisible({ timeout: 10000 });\n  511 |     // Should see at least outstanding and partially_settled badges\n    at /var/www/360lm/tests/fundcustodian.spec.js:508:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:06:12.594Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-d5daa--partially-settled-settled--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-d5daa--partially-settled-settled--android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 371,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30062,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 508
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  506 |     await page.goto(BASE_URL);\n  507 |     await handleModeSelector(page, 'overseer');\n> 508 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  509 |     await page.locator('#tab-advances').click();\n  510 |     await expect(page.locator('#advances-list .ledger-row').first()).toBeVisible({ timeout: 10000 });\n  511 |     // Should see at least outstanding and partially_settled badges\n    at /var/www/360lm/tests/fundcustodian.spec.js:508:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:06:43.662Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-d5daa--partially-settled-settled--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-d5daa--partially-settled-settled--android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-bf6b30ae3c42f031b684",
              "file": "fundcustodian.spec.js",
              "line": 504,
              "column": 3
            },
            {
              "title": "partially settled advance shows outstanding amount",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 372,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 520
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  518 |     await injectSession(page, SESSION.shambhu);\n  519 |     await page.goto(BASE_URL);\n> 520 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  521 |     await page.locator('#tab-advances').click();\n  522 |     await expect(page.locator('#advances-list').first()).toBeVisible({ timeout: 10000 });\n  523 |     // A2: Sachin 2500 partially settled, outstanding 1500\n    at /var/www/360lm/tests/fundcustodian.spec.js:520:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:07:14.636Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-8f04d-ce-shows-outstanding-amount-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-8f04d-ce-shows-outstanding-amount-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 373,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 520
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  518 |     await injectSession(page, SESSION.shambhu);\n  519 |     await page.goto(BASE_URL);\n> 520 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  521 |     await page.locator('#tab-advances').click();\n  522 |     await expect(page.locator('#advances-list').first()).toBeVisible({ timeout: 10000 });\n  523 |     // A2: Sachin 2500 partially settled, outstanding 1500\n    at /var/www/360lm/tests/fundcustodian.spec.js:520:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:07:45.645Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-8f04d-ce-shows-outstanding-amount-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-8f04d-ce-shows-outstanding-amount-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-05263987a6087fa1405e",
              "file": "fundcustodian.spec.js",
              "line": 517,
              "column": 3
            },
            {
              "title": "advances show status badges (outstanding / partially_settled / settled)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1377,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 508
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  506 |     await page.goto(BASE_URL);\n  507 |     await handleModeSelector(page, 'overseer');\n> 508 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  509 |     await page.locator('#tab-advances').click();\n  510 |     await expect(page.locator('#advances-list .ledger-row').first()).toBeVisible({ timeout: 10000 });\n  511 |     // Should see at least outstanding and partially_settled badges\n    at /var/www/360lm/tests/fundcustodian.spec.js:508:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:05:57.897Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-d5daa--partially-settled-settled--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-d5daa--partially-settled-settled--desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1378,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 508
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  506 |     await page.goto(BASE_URL);\n  507 |     await handleModeSelector(page, 'overseer');\n> 508 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  509 |     await page.locator('#tab-advances').click();\n  510 |     await expect(page.locator('#advances-list .ledger-row').first()).toBeVisible({ timeout: 10000 });\n  511 |     // Should see at least outstanding and partially_settled badges\n    at /var/www/360lm/tests/fundcustodian.spec.js:508:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:06:29.034Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-d5daa--partially-settled-settled--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-d5daa--partially-settled-settled--desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-fbe477291331dbb17c6e",
              "file": "fundcustodian.spec.js",
              "line": 504,
              "column": 3
            },
            {
              "title": "partially settled advance shows outstanding amount",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1379,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30083,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 520
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  518 |     await injectSession(page, SESSION.shambhu);\n  519 |     await page.goto(BASE_URL);\n> 520 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  521 |     await page.locator('#tab-advances').click();\n  522 |     await expect(page.locator('#advances-list').first()).toBeVisible({ timeout: 10000 });\n  523 |     // A2: Sachin 2500 partially settled, outstanding 1500\n    at /var/www/360lm/tests/fundcustodian.spec.js:520:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:07:00.344Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-8f04d-ce-shows-outstanding-amount-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-8f04d-ce-shows-outstanding-amount-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1380,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30176,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 520
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  518 |     await injectSession(page, SESSION.shambhu);\n  519 |     await page.goto(BASE_URL);\n> 520 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  521 |     await page.locator('#tab-advances').click();\n  522 |     await expect(page.locator('#advances-list').first()).toBeVisible({ timeout: 10000 });\n  523 |     // A2: Sachin 2500 partially settled, outstanding 1500\n    at /var/www/360lm/tests/fundcustodian.spec.js:520:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:07:31.531Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-8f04d-ce-shows-outstanding-amount-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-—-Adv-8f04d-ce-shows-outstanding-amount-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-0a704304aab8aaebf84a",
              "file": "fundcustodian.spec.js",
              "line": 517,
              "column": 3
            }
          ]
        },
        {
          "title": "Manage Custodians (admin only)",
          "file": "fundcustodian.spec.js",
          "line": 532,
          "column": 6,
          "specs": [
            {
              "title": "custodian list loads all 4 custodians",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 374,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8302,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:08:16.803Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-80c32-list-loads-all-4-custodians-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-80c32-list-loads-all-4-custodians-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 375,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8277,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:08:26.109Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-80c32-list-loads-all-4-custodians-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-80c32-list-loads-all-4-custodians-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-24a5ba9ed0bb01036ea0",
              "file": "fundcustodian.spec.js",
              "line": 543,
              "column": 3
            },
            {
              "title": "primary custodian shows ⭐ Primary badge",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 376,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8249,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:08:35.350Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-52df2-odian-shows-⭐-Primary-badge-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-52df2-odian-shows-⭐-Primary-badge-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 377,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8261,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:08:44.515Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-52df2-odian-shows-⭐-Primary-badge-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-52df2-odian-shows-⭐-Primary-badge-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-d0c9ca9097e82f4d314d",
              "file": "fundcustodian.spec.js",
              "line": 549,
              "column": 3
            },
            {
              "title": "add custodian form — empty ID shows error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 378,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8664,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:08:53.818Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-46cc6-form-—-empty-ID-shows-error-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-46cc6-form-—-empty-ID-shows-error-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 379,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8463,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:09:04.641Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-46cc6-form-—-empty-ID-shows-error-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-46cc6-form-—-empty-ID-shows-error-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-39d6c9c5dd6eed803eb7",
              "file": "fundcustodian.spec.js",
              "line": 554,
              "column": 3
            },
            {
              "title": "add custodian form — empty name shows error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 380,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:09:14.267Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-b8813-rm-—-empty-name-shows-error-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-b8813-rm-—-empty-name-shows-error-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 381,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8274,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:09:23.635Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-b8813-rm-—-empty-name-shows-error-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-b8813-rm-—-empty-name-shows-error-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-d10e902cdd4122cdb627",
              "file": "fundcustodian.spec.js",
              "line": 560,
              "column": 3
            },
            {
              "title": "back button returns to dashboard",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 382,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8281,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:09:32.989Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-6a9fb-button-returns-to-dashboard-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-6a9fb-button-returns-to-dashboard-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 383,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8277,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:09:42.331Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-6a9fb-button-returns-to-dashboard-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-6a9fb-button-returns-to-dashboard-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-0b06a2728ba51186727f",
              "file": "fundcustodian.spec.js",
              "line": 566,
              "column": 3
            },
            {
              "title": "non-admin cannot reach manage screen via nav",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 384,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8294,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:09:51.698Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-82e4b-reach-manage-screen-via-nav-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-82e4b-reach-manage-screen-via-nav-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 385,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8838,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:10:01.416Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-82e4b-reach-manage-screen-via-nav-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-82e4b-reach-manage-screen-via-nav-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-d2ff9d722362f27be951",
              "file": "fundcustodian.spec.js",
              "line": 571,
              "column": 3
            },
            {
              "title": "custodian list loads all 4 custodians",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1381,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8373,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:08:03.630Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-80c32-list-loads-all-4-custodians-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-80c32-list-loads-all-4-custodians-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 1382,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8286,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:08:13.094Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-80c32-list-loads-all-4-custodians-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-80c32-list-loads-all-4-custodians-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-93c2ee71dd3329528b9d",
              "file": "fundcustodian.spec.js",
              "line": 543,
              "column": 3
            },
            {
              "title": "primary custodian shows ⭐ Primary badge",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1383,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:08:22.463Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-52df2-odian-shows-⭐-Primary-badge-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-52df2-odian-shows-⭐-Primary-badge-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 1384,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8343,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:08:31.831Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-52df2-odian-shows-⭐-Primary-badge-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-52df2-odian-shows-⭐-Primary-badge-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-53baee608415d70d0402",
              "file": "fundcustodian.spec.js",
              "line": 549,
              "column": 3
            },
            {
              "title": "add custodian form — empty ID shows error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1385,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8301,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:08:41.205Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-46cc6-form-—-empty-ID-shows-error-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-46cc6-form-—-empty-ID-shows-error-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 1386,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8278,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:08:50.565Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-46cc6-form-—-empty-ID-shows-error-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-46cc6-form-—-empty-ID-shows-error-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-af2638cfeeb8c5dda02e",
              "file": "fundcustodian.spec.js",
              "line": 554,
              "column": 3
            },
            {
              "title": "add custodian form — empty name shows error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1387,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8474,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:08:59.845Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-b8813-rm-—-empty-name-shows-error-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-b8813-rm-—-empty-name-shows-error-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 1388,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8317,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:09:09.445Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-b8813-rm-—-empty-name-shows-error-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-b8813-rm-—-empty-name-shows-error-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-297b829ff49d7f5eec1e",
              "file": "fundcustodian.spec.js",
              "line": 560,
              "column": 3
            },
            {
              "title": "back button returns to dashboard",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1389,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8293,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:09:18.869Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-6a9fb-button-returns-to-dashboard-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-6a9fb-button-returns-to-dashboard-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 1390,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8293,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:09:28.169Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-6a9fb-button-returns-to-dashboard-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-6a9fb-button-returns-to-dashboard-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-7e5e194816d8d75cc7d8",
              "file": "fundcustodian.spec.js",
              "line": 566,
              "column": 3
            },
            {
              "title": "non-admin cannot reach manage screen via nav",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1391,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8313,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:09:37.519Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-82e4b-reach-manage-screen-via-nav-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-82e4b-reach-manage-screen-via-nav-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    },
                    {
                      "workerIndex": 1392,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8300,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 51,
                          "line": 538
                        },
                        "snippet": "  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 51,
                            "line": 538
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#manage-hdr-btn')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#manage-hdr-btn')\u001b[22m\n\n\n  536 |     await handleModeSelector(page, 'overseer');\n  537 |     // Navigate via header button (visible only in overseer mode)\n> 538 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 8000 });\n      |                                                   ^\n  539 |     await page.locator('#manage-hdr-btn').click();\n  540 |     await expect(page.locator('#s-manage')).toHaveClass(/active/);\n  541 |   });\n    at /var/www/360lm/tests/fundcustodian.spec.js:538:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:09:46.868Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-82e4b-reach-manage-screen-via-nav-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-Custo-82e4b-reach-manage-screen-via-nav-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 51,
                        "line": 538
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-96b51e50ce4e2d6578c7",
              "file": "fundcustodian.spec.js",
              "line": 571,
              "column": 3
            }
          ]
        },
        {
          "title": "Reject Transfer Modal",
          "file": "fundcustodian.spec.js",
          "line": 582,
          "column": 6,
          "specs": [
            {
              "title": "clicking reject button opens reject modal",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 386,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10289,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:587:55",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 55,
                          "line": 587
                        },
                        "snippet": "  585 |     await page.goto(BASE_URL);\n  586 |     await handleModeSelector(page, 'overseer');\n> 587 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  588 |     await page.locator('.btn-reject').first().click();\n  589 |     await expect(page.locator('#reject-modal')).toHaveClass(/open/);\n  590 |     await expect(page.locator('#reject-modal .reject-sheet h3')).toContainText('Reject Transfer');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 55,
                            "line": 587
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n\n  585 |     await page.goto(BASE_URL);\n  586 |     await handleModeSelector(page, 'overseer');\n> 587 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  588 |     await page.locator('.btn-reject').first().click();\n  589 |     await expect(page.locator('#reject-modal')).toHaveClass(/open/);\n  590 |     await expect(page.locator('#reject-modal .reject-sheet h3')).toContainText('Reject Transfer');\n    at /var/www/360lm/tests/fundcustodian.spec.js:587:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:10:11.620Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-537e3-t-button-opens-reject-modal-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-537e3-t-button-opens-reject-modal-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 55,
                        "line": 587
                      }
                    },
                    {
                      "workerIndex": 387,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10279,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:587:55",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 55,
                          "line": 587
                        },
                        "snippet": "  585 |     await page.goto(BASE_URL);\n  586 |     await handleModeSelector(page, 'overseer');\n> 587 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  588 |     await page.locator('.btn-reject').first().click();\n  589 |     await expect(page.locator('#reject-modal')).toHaveClass(/open/);\n  590 |     await expect(page.locator('#reject-modal .reject-sheet h3')).toContainText('Reject Transfer');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 55,
                            "line": 587
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n\n  585 |     await page.goto(BASE_URL);\n  586 |     await handleModeSelector(page, 'overseer');\n> 587 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  588 |     await page.locator('.btn-reject').first().click();\n  589 |     await expect(page.locator('#reject-modal')).toHaveClass(/open/);\n  590 |     await expect(page.locator('#reject-modal .reject-sheet h3')).toContainText('Reject Transfer');\n    at /var/www/360lm/tests/fundcustodian.spec.js:587:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:10:22.918Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-537e3-t-button-opens-reject-modal-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-537e3-t-button-opens-reject-modal-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 55,
                        "line": 587
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-c0f89785aa80f2dc2453",
              "file": "fundcustodian.spec.js",
              "line": 583,
              "column": 3
            },
            {
              "title": "cancel button closes reject modal",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 388,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10274,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:597:55",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 55,
                          "line": 597
                        },
                        "snippet": "  595 |     await page.goto(BASE_URL);\n  596 |     await handleModeSelector(page, 'overseer');\n> 597 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  598 |     await page.locator('.btn-reject').first().click();\n  599 |     // Scope to #reject-modal to avoid strict-mode conflict with #edit-payee-modal cancel\n  600 |     await page.locator('#reject-modal .reject-btns .btn-s').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 55,
                            "line": 597
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n\n  595 |     await page.goto(BASE_URL);\n  596 |     await handleModeSelector(page, 'overseer');\n> 597 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  598 |     await page.locator('.btn-reject').first().click();\n  599 |     // Scope to #reject-modal to avoid strict-mode conflict with #edit-payee-modal cancel\n  600 |     await page.locator('#reject-modal .reject-btns .btn-s').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:597:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:10:34.281Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-9dcda--button-closes-reject-modal-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-9dcda--button-closes-reject-modal-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 55,
                        "line": 597
                      }
                    },
                    {
                      "workerIndex": 389,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10262,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:597:55",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 55,
                          "line": 597
                        },
                        "snippet": "  595 |     await page.goto(BASE_URL);\n  596 |     await handleModeSelector(page, 'overseer');\n> 597 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  598 |     await page.locator('.btn-reject').first().click();\n  599 |     // Scope to #reject-modal to avoid strict-mode conflict with #edit-payee-modal cancel\n  600 |     await page.locator('#reject-modal .reject-btns .btn-s').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 55,
                            "line": 597
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n\n  595 |     await page.goto(BASE_URL);\n  596 |     await handleModeSelector(page, 'overseer');\n> 597 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  598 |     await page.locator('.btn-reject').first().click();\n  599 |     // Scope to #reject-modal to avoid strict-mode conflict with #edit-payee-modal cancel\n  600 |     await page.locator('#reject-modal .reject-btns .btn-s').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:597:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:10:45.540Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-9dcda--button-closes-reject-modal-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-9dcda--button-closes-reject-modal-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 55,
                        "line": 597
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-9f82e9d3279becc0f6d4",
              "file": "fundcustodian.spec.js",
              "line": 593,
              "column": 3
            },
            {
              "title": "clicking reject button opens reject modal",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1393,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10348,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:587:55",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 55,
                          "line": 587
                        },
                        "snippet": "  585 |     await page.goto(BASE_URL);\n  586 |     await handleModeSelector(page, 'overseer');\n> 587 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  588 |     await page.locator('.btn-reject').first().click();\n  589 |     await expect(page.locator('#reject-modal')).toHaveClass(/open/);\n  590 |     await expect(page.locator('#reject-modal .reject-sheet h3')).toContainText('Reject Transfer');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 55,
                            "line": 587
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n\n  585 |     await page.goto(BASE_URL);\n  586 |     await handleModeSelector(page, 'overseer');\n> 587 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  588 |     await page.locator('.btn-reject').first().click();\n  589 |     await expect(page.locator('#reject-modal')).toHaveClass(/open/);\n  590 |     await expect(page.locator('#reject-modal .reject-sheet h3')).toContainText('Reject Transfer');\n    at /var/www/360lm/tests/fundcustodian.spec.js:587:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:09:56.207Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-537e3-t-button-opens-reject-modal-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-537e3-t-button-opens-reject-modal-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 55,
                        "line": 587
                      }
                    },
                    {
                      "workerIndex": 1394,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10334,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:587:55",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 55,
                          "line": 587
                        },
                        "snippet": "  585 |     await page.goto(BASE_URL);\n  586 |     await handleModeSelector(page, 'overseer');\n> 587 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  588 |     await page.locator('.btn-reject').first().click();\n  589 |     await expect(page.locator('#reject-modal')).toHaveClass(/open/);\n  590 |     await expect(page.locator('#reject-modal .reject-sheet h3')).toContainText('Reject Transfer');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 55,
                            "line": 587
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n\n  585 |     await page.goto(BASE_URL);\n  586 |     await handleModeSelector(page, 'overseer');\n> 587 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  588 |     await page.locator('.btn-reject').first().click();\n  589 |     await expect(page.locator('#reject-modal')).toHaveClass(/open/);\n  590 |     await expect(page.locator('#reject-modal .reject-sheet h3')).toContainText('Reject Transfer');\n    at /var/www/360lm/tests/fundcustodian.spec.js:587:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:10:07.824Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-537e3-t-button-opens-reject-modal-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-537e3-t-button-opens-reject-modal-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 55,
                        "line": 587
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-17feaf856c6481abb7a8",
              "file": "fundcustodian.spec.js",
              "line": 583,
              "column": 3
            },
            {
              "title": "cancel button closes reject modal",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1395,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10315,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:597:55",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 55,
                          "line": 597
                        },
                        "snippet": "  595 |     await page.goto(BASE_URL);\n  596 |     await handleModeSelector(page, 'overseer');\n> 597 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  598 |     await page.locator('.btn-reject').first().click();\n  599 |     // Scope to #reject-modal to avoid strict-mode conflict with #edit-payee-modal cancel\n  600 |     await page.locator('#reject-modal .reject-btns .btn-s').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 55,
                            "line": 597
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n\n  595 |     await page.goto(BASE_URL);\n  596 |     await handleModeSelector(page, 'overseer');\n> 597 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  598 |     await page.locator('.btn-reject').first().click();\n  599 |     // Scope to #reject-modal to avoid strict-mode conflict with #edit-payee-modal cancel\n  600 |     await page.locator('#reject-modal .reject-btns .btn-s').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:597:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:10:19.327Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-9dcda--button-closes-reject-modal-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-9dcda--button-closes-reject-modal-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 55,
                        "line": 597
                      }
                    },
                    {
                      "workerIndex": 1396,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10317,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:597:55",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 55,
                          "line": 597
                        },
                        "snippet": "  595 |     await page.goto(BASE_URL);\n  596 |     await handleModeSelector(page, 'overseer');\n> 597 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  598 |     await page.locator('.btn-reject').first().click();\n  599 |     // Scope to #reject-modal to avoid strict-mode conflict with #edit-payee-modal cancel\n  600 |     await page.locator('#reject-modal .reject-btns .btn-s').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 55,
                            "line": 597
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-reject').first()\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-reject').first()\u001b[22m\n\n\n  595 |     await page.goto(BASE_URL);\n  596 |     await handleModeSelector(page, 'overseer');\n> 597 |     await expect(page.locator('.btn-reject').first()).toBeVisible({ timeout: 10000 });\n      |                                                       ^\n  598 |     await page.locator('.btn-reject').first().click();\n  599 |     // Scope to #reject-modal to avoid strict-mode conflict with #edit-payee-modal cancel\n  600 |     await page.locator('#reject-modal .reject-btns .btn-s').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:597:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:10:30.802Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-9dcda--button-closes-reject-modal-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Reject-Trans-9dcda--button-closes-reject-modal-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 55,
                        "line": 597
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-21e56049a73a4b288c61",
              "file": "fundcustodian.spec.js",
              "line": 593,
              "column": 3
            }
          ]
        },
        {
          "title": "Bottom Navigation",
          "file": "fundcustodian.spec.js",
          "line": 608,
          "column": 6,
          "specs": [
            {
              "title": "nav → Transfer shows transfer screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 390,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8271,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:10:56.785Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-eb3ef-nsfer-shows-transfer-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-eb3ef-nsfer-shows-transfer-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    },
                    {
                      "workerIndex": 391,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8286,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:11:06.151Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-eb3ef-nsfer-shows-transfer-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-eb3ef-nsfer-shows-transfer-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-1fe5c51479cb627e6017",
              "file": "fundcustodian.spec.js",
              "line": 615,
              "column": 3
            },
            {
              "title": "Advance nav tab no longer exists (merged into Transfer)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 392,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8249,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:11:15.423Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-e65d1-xists-merged-into-Transfer--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-e65d1-xists-merged-into-Transfer--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    },
                    {
                      "workerIndex": 393,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8243,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:11:24.661Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-e65d1-xists-merged-into-Transfer--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-e65d1-xists-merged-into-Transfer--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-f4583260ac780b51ee58",
              "file": "fundcustodian.spec.js",
              "line": 622,
              "column": 3
            },
            {
              "title": "nav → Ledger shows ledger screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 394,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8242,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:11:33.846Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-9edc4--Ledger-shows-ledger-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-9edc4--Ledger-shows-ledger-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    },
                    {
                      "workerIndex": 395,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8248,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:11:43.038Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-9edc4--Ledger-shows-ledger-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-9edc4--Ledger-shows-ledger-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-b6162d87517fdb5f7f64",
              "file": "fundcustodian.spec.js",
              "line": 626,
              "column": 3
            },
            {
              "title": "nav → Dashboard returns home",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 396,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8242,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:11:52.360Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navigation-nav-→-Dashboard-returns-home-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navigation-nav-→-Dashboard-returns-home-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    },
                    {
                      "workerIndex": 397,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8416,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:12:02.291Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navigation-nav-→-Dashboard-returns-home-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navigation-nav-→-Dashboard-returns-home-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-551b6bf70781ebd5731f",
              "file": "fundcustodian.spec.js",
              "line": 632,
              "column": 3
            },
            {
              "title": "Hub button is visible on every screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 398,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8248,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:12:11.924Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-43240--is-visible-on-every-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-43240--is-visible-on-every-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    },
                    {
                      "workerIndex": 399,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8237,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:12:21.242Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-43240--is-visible-on-every-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-43240--is-visible-on-every-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-db2bab9ebcfa601ddfc3",
              "file": "fundcustodian.spec.js",
              "line": 639,
              "column": 3
            },
            {
              "title": "nav → Transfer shows transfer screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1397,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8234,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:10:42.261Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-eb3ef-nsfer-shows-transfer-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-eb3ef-nsfer-shows-transfer-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    },
                    {
                      "workerIndex": 1398,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8237,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:10:51.498Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-eb3ef-nsfer-shows-transfer-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-eb3ef-nsfer-shows-transfer-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-1403f351436cbb8d6daa",
              "file": "fundcustodian.spec.js",
              "line": 615,
              "column": 3
            },
            {
              "title": "Advance nav tab no longer exists (merged into Transfer)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1399,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8429,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:11:01.017Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-e65d1-xists-merged-into-Transfer--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-e65d1-xists-merged-into-Transfer--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    },
                    {
                      "workerIndex": 1400,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8248,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:11:10.489Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-e65d1-xists-merged-into-Transfer--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-e65d1-xists-merged-into-Transfer--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-37b1b3b1c93edc1f301c",
              "file": "fundcustodian.spec.js",
              "line": 622,
              "column": 3
            },
            {
              "title": "nav → Ledger shows ledger screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1401,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8256,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:11:19.844Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-9edc4--Ledger-shows-ledger-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-9edc4--Ledger-shows-ledger-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    },
                    {
                      "workerIndex": 1402,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8301,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:11:29.237Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-9edc4--Ledger-shows-ledger-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-9edc4--Ledger-shows-ledger-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-567dd23c0791430f34c0",
              "file": "fundcustodian.spec.js",
              "line": 626,
              "column": 3
            },
            {
              "title": "nav → Dashboard returns home",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1403,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8259,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:11:38.586Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navigation-nav-→-Dashboard-returns-home-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navigation-nav-→-Dashboard-returns-home-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    },
                    {
                      "workerIndex": 1404,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8258,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:11:47.894Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navigation-nav-→-Dashboard-returns-home-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navigation-nav-→-Dashboard-returns-home-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-e4a4288e8881ff9d44df",
              "file": "fundcustodian.spec.js",
              "line": 632,
              "column": 3
            },
            {
              "title": "Hub button is visible on every screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1405,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8284,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:11:57.274Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-43240--is-visible-on-every-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-43240--is-visible-on-every-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    },
                    {
                      "workerIndex": 1406,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8260,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 612
                        },
                        "snippet": "  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 612
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  610 |     await injectSession(page, SESSION.shambhu);\n  611 |     await page.goto(BASE_URL);\n> 612 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  613 |   });\n  614 |\n  615 |   test('nav → Transfer shows transfer screen', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:612:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:12:06.727Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-43240--is-visible-on-every-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Bottom-Navig-43240--is-visible-on-every-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 612
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-df6b398b147755b5374a",
              "file": "fundcustodian.spec.js",
              "line": 639,
              "column": 3
            }
          ]
        },
        {
          "title": "Status Badge Rendering",
          "file": "fundcustodian.spec.js",
          "line": 650,
          "column": 6,
          "specs": [
            {
              "title": "admin ledger shows all expected transfer statuses",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 400,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30105,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 655
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  653 |     await page.goto(BASE_URL);\n  654 |     await handleModeSelector(page, 'overseer');\n> 655 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  656 |     await page.locator('#tab-transfers').click();\n  657 |     await expect(page.locator('#transfers-list .ledger-row').first()).toBeVisible({ timeout: 10000 });\n  658 |     const badgeTexts = await page.locator('#transfers-list .badge').allTextContents();\n    at /var/www/360lm/tests/fundcustodian.spec.js:655:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:12:30.561Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Status-Badge-a6816--expected-transfer-statuses-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Status-Badge-a6816--expected-transfer-statuses-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 401,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 655
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  653 |     await page.goto(BASE_URL);\n  654 |     await handleModeSelector(page, 'overseer');\n> 655 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  656 |     await page.locator('#tab-transfers').click();\n  657 |     await expect(page.locator('#transfers-list .ledger-row').first()).toBeVisible({ timeout: 10000 });\n  658 |     const badgeTexts = await page.locator('#transfers-list .badge').allTextContents();\n    at /var/www/360lm/tests/fundcustodian.spec.js:655:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:13:01.874Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Status-Badge-a6816--expected-transfer-statuses-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Status-Badge-a6816--expected-transfer-statuses-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-23285101eada7bb8d05b",
              "file": "fundcustodian.spec.js",
              "line": 651,
              "column": 3
            },
            {
              "title": "admin ledger shows all expected transfer statuses",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1407,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30075,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 655
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  653 |     await page.goto(BASE_URL);\n  654 |     await handleModeSelector(page, 'overseer');\n> 655 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  656 |     await page.locator('#tab-transfers').click();\n  657 |     await expect(page.locator('#transfers-list .ledger-row').first()).toBeVisible({ timeout: 10000 });\n  658 |     const badgeTexts = await page.locator('#transfers-list .badge').allTextContents();\n    at /var/www/360lm/tests/fundcustodian.spec.js:655:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:12:15.994Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Status-Badge-a6816--expected-transfer-statuses-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Status-Badge-a6816--expected-transfer-statuses-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1408,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30085,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 655
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  653 |     await page.goto(BASE_URL);\n  654 |     await handleModeSelector(page, 'overseer');\n> 655 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  656 |     await page.locator('#tab-transfers').click();\n  657 |     await expect(page.locator('#transfers-list .ledger-row').first()).toBeVisible({ timeout: 10000 });\n  658 |     const badgeTexts = await page.locator('#transfers-list .badge').allTextContents();\n    at /var/www/360lm/tests/fundcustodian.spec.js:655:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:12:47.125Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Status-Badge-a6816--expected-transfer-statuses-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Status-Badge-a6816--expected-transfer-statuses-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-6d6b62b2b820568852bd",
              "file": "fundcustodian.spec.js",
              "line": 651,
              "column": 3
            }
          ]
        },
        {
          "title": "Manage — Payees & Reconcile (admin)",
          "file": "fundcustodian.spec.js",
          "line": 668,
          "column": 6,
          "specs": [
            {
              "title": "three manage sub-tabs present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 402,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8256,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:13:33.207Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-4ee6f-ree-manage-sub-tabs-present-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-4ee6f-ree-manage-sub-tabs-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    },
                    {
                      "workerIndex": 403,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8246,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:13:42.558Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-4ee6f-ree-manage-sub-tabs-present-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-4ee6f-ree-manage-sub-tabs-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-f21fb8cdc51efa45d5aa",
              "file": "fundcustodian.spec.js",
              "line": 679,
              "column": 3
            },
            {
              "title": "Payees tab lists employees and supports category switch",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 404,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8294,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:13:51.836Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-9e9de-nd-supports-category-switch-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-9e9de-nd-supports-category-switch-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    },
                    {
                      "workerIndex": 405,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8274,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:14:01.237Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-9e9de-nd-supports-category-switch-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-9e9de-nd-supports-category-switch-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-0e5b7473af73a4d2e4ba",
              "file": "fundcustodian.spec.js",
              "line": 685,
              "column": 3
            },
            {
              "title": "Edit payee modal opens with prefilled data",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 406,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8267,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:14:10.531Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-69cf5-l-opens-with-prefilled-data-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-69cf5-l-opens-with-prefilled-data-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    },
                    {
                      "workerIndex": 407,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8243,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:14:20.028Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-69cf5-l-opens-with-prefilled-data-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-69cf5-l-opens-with-prefilled-data-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-1684b4bda2d36ad008a6",
              "file": "fundcustodian.spec.js",
              "line": 694,
              "column": 3
            },
            {
              "title": "Reconcile tab: validation requires narration",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 408,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8259,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:14:29.365Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-0fd87-lidation-requires-narration-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-0fd87-lidation-requires-narration-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    },
                    {
                      "workerIndex": 409,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8259,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:14:38.625Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-0fd87-lidation-requires-narration-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-0fd87-lidation-requires-narration-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-5e14a795977ade5d232f",
              "file": "fundcustodian.spec.js",
              "line": 704,
              "column": 3
            },
            {
              "title": "Add Payee form requires a name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 410,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8286,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:14:47.867Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-65398--Payee-form-requires-a-name-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-65398--Payee-form-requires-a-name-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    },
                    {
                      "workerIndex": 411,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8370,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:14:57.161Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-65398--Payee-form-requires-a-name-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-65398--Payee-form-requires-a-name-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-e9fd6bb2660e627400cb",
              "file": "fundcustodian.spec.js",
              "line": 713,
              "column": 3
            },
            {
              "title": "three manage sub-tabs present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1409,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8266,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:13:18.285Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-4ee6f-ree-manage-sub-tabs-present-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-4ee6f-ree-manage-sub-tabs-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    },
                    {
                      "workerIndex": 1410,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8261,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:13:27.591Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-4ee6f-ree-manage-sub-tabs-present-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-4ee6f-ree-manage-sub-tabs-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-77d140a929530f0c2533",
              "file": "fundcustodian.spec.js",
              "line": 679,
              "column": 3
            },
            {
              "title": "Payees tab lists employees and supports category switch",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1411,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:13:36.920Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-9e9de-nd-supports-category-switch-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-9e9de-nd-supports-category-switch-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    },
                    {
                      "workerIndex": 1412,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8276,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:13:46.219Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-9e9de-nd-supports-category-switch-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-9e9de-nd-supports-category-switch-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-f1e4dac28885901d8ce7",
              "file": "fundcustodian.spec.js",
              "line": 685,
              "column": 3
            },
            {
              "title": "Edit payee modal opens with prefilled data",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1413,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8312,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:13:55.574Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-69cf5-l-opens-with-prefilled-data-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-69cf5-l-opens-with-prefilled-data-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    },
                    {
                      "workerIndex": 1414,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8281,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:14:05.182Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-69cf5-l-opens-with-prefilled-data-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-69cf5-l-opens-with-prefilled-data-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-ea920611230d0951cdaa",
              "file": "fundcustodian.spec.js",
              "line": 694,
              "column": 3
            },
            {
              "title": "Reconcile tab: validation requires narration",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1415,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8245,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:14:14.525Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-0fd87-lidation-requires-narration-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-0fd87-lidation-requires-narration-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    },
                    {
                      "workerIndex": 1416,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8279,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:14:23.843Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-0fd87-lidation-requires-narration-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-0fd87-lidation-requires-narration-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-1831ee562f2518e48f82",
              "file": "fundcustodian.spec.js",
              "line": 704,
              "column": 3
            },
            {
              "title": "Add Payee form requires a name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1417,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8239,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:14:33.114Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-65398--Payee-form-requires-a-name-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-65398--Payee-form-requires-a-name-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    },
                    {
                      "workerIndex": 1418,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8246,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 43,
                          "line": 672
                        },
                        "snippet": "  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 43,
                            "line": 672
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash')\u001b[22m\n\n\n  670 |     await injectSession(page, SESSION.pramod);\n  671 |     await page.goto(BASE_URL);\n> 672 |     await expect(page.locator('#s-dash')).toHaveClass(/active/, { timeout: 8000 });\n      |                                           ^\n  673 |     await handleModeSelector(page, 'overseer');\n  674 |     await expect(page.locator('#manage-hdr-btn')).toBeVisible({ timeout: 5000 });\n  675 |     await page.locator('#manage-hdr-btn').click();\n    at /var/www/360lm/tests/fundcustodian.spec.js:672:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:14:42.402Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-65398--Payee-form-requires-a-name-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Manage-—-Pay-65398--Payee-form-requires-a-name-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 43,
                        "line": 672
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-ce69985ed41a30cf6a3e",
              "file": "fundcustodian.spec.js",
              "line": 713,
              "column": 3
            }
          ]
        },
        {
          "title": "Cash-return RPC guards (web_anon)",
          "file": "fundcustodian.spec.js",
          "line": 726,
          "column": 6,
          "specs": [
            {
              "title": "settle_advance_by_cash is reachable and guards a missing advance",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 412,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 122,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:15:06.876Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "1caa50027b536c34a40f-5add335f8abc5beb43e6",
              "file": "fundcustodian.spec.js",
              "line": 727,
              "column": 3
            },
            {
              "title": "rejects a non-positive amount",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 412,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 34,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:15:07.055Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "1caa50027b536c34a40f-53d914ea8c33fc0399f5",
              "file": "fundcustodian.spec.js",
              "line": 738,
              "column": 3
            },
            {
              "title": "settle_advance_by_cash is reachable and guards a missing advance",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1419,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 113,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:14:51.694Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "1caa50027b536c34a40f-f0adfe80698b55772d9c",
              "file": "fundcustodian.spec.js",
              "line": 727,
              "column": 3
            },
            {
              "title": "rejects a non-positive amount",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1419,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 18,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:14:51.854Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "1caa50027b536c34a40f-2ba5f1bd29b759265ffc",
              "file": "fundcustodian.spec.js",
              "line": 738,
              "column": 3
            }
          ]
        },
        {
          "title": "Cash-return UI (receiving + employee)",
          "file": "fundcustodian.spec.js",
          "line": 747,
          "column": 6,
          "specs": [
            {
              "title": "Receiving + Employee shows people search (chooser appears only with advances)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 412,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 748
                        },
                        "snippet": "  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 748
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 752
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n> 752 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  753 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  754 |   });\n  755 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:752:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:15:07.118Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--13a19-appears-only-with-advances--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--13a19-appears-only-with-advances--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 748
                      }
                    },
                    {
                      "workerIndex": 413,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30087,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 748
                        },
                        "snippet": "  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 748
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 752
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n> 752 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  753 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  754 |   });\n  755 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:752:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:15:38.222Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--13a19-appears-only-with-advances--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--13a19-appears-only-with-advances--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 748
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-55e73bbd26eb6271a2c6",
              "file": "fundcustodian.spec.js",
              "line": 756,
              "column": 3
            },
            {
              "title": "advance-type chips hidden when receiving (only for giving)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 414,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 748
                        },
                        "snippet": "  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 748
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 752
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n> 752 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  753 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  754 |   });\n  755 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:752:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:16:09.425Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--2eedf--receiving-only-for-giving--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--2eedf--receiving-only-for-giving--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 748
                      }
                    },
                    {
                      "workerIndex": 415,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 748
                        },
                        "snippet": "  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 748
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 752
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n> 752 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  753 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  754 |   });\n  755 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:752:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:16:40.532Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--2eedf--receiving-only-for-giving--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--2eedf--receiving-only-for-giving--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 748
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-0d093df0867f513cf2ee",
              "file": "fundcustodian.spec.js",
              "line": 765,
              "column": 3
            },
            {
              "title": "Receiving + Employee shows people search (chooser appears only with advances)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1419,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30080,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 748
                        },
                        "snippet": "  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 748
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 752
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n> 752 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  753 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  754 |   });\n  755 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:752:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:14:51.905Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--13a19-appears-only-with-advances--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--13a19-appears-only-with-advances--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 748
                      }
                    },
                    {
                      "workerIndex": 1420,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30132,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 748
                        },
                        "snippet": "  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 748
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 752
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n> 752 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  753 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  754 |   });\n  755 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:752:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:15:23.076Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--13a19-appears-only-with-advances--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--13a19-appears-only-with-advances--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 748
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-595e4d0f3812df880060",
              "file": "fundcustodian.spec.js",
              "line": 756,
              "column": 3
            },
            {
              "title": "advance-type chips hidden when receiving (only for giving)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1421,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30095,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 748
                        },
                        "snippet": "  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 748
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 752
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n> 752 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  753 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  754 |   });\n  755 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:752:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:15:54.304Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--2eedf--receiving-only-for-giving--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--2eedf--receiving-only-for-giving--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 748
                      }
                    },
                    {
                      "workerIndex": 1422,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 8,
                          "line": 748
                        },
                        "snippet": "  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 8,
                            "line": 748
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  746 |\n  747 | test.describe('Cash-return UI (receiving + employee)', () => {\n> 748 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  749 |     await injectSession(page, SESSION.pramod);\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n    at /var/www/360lm/tests/fundcustodian.spec.js:748:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 41,
                            "line": 752
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  750 |     await page.goto(BASE_URL);\n  751 |     await handleModeSelector(page, 'custodian');\n> 752 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  753 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  754 |   });\n  755 |\n    at /var/www/360lm/tests/fundcustodian.spec.js:752:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:16:25.440Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--2eedf--receiving-only-for-giving--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Cash-return--2eedf--receiving-only-for-giving--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 8,
                        "line": 748
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-394d5510470d006cc3c2",
              "file": "fundcustodian.spec.js",
              "line": 765,
              "column": 3
            }
          ]
        },
        {
          "title": "Ledger Account tab still ties to wallet (settlements now shown)",
          "file": "fundcustodian.spec.js",
          "line": 772,
          "column": 6,
          "specs": [
            {
              "title": "account running balance renders for admin without error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 416,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 777
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  775 |     await page.goto(BASE_URL);\n  776 |     await handleModeSelector(page, 'custodian');\n> 777 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  778 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/, { timeout: 4000 });\n  779 |     await page.locator('#tab-account').click();\n  780 |     await expect(page.locator('#ledger-account-panel')).toBeVisible();\n    at /var/www/360lm/tests/fundcustodian.spec.js:777:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:17:11.581Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-Accou-84a20-ers-for-admin-without-error-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-Accou-84a20-ers-for-admin-without-error-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 417,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30067,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 777
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  775 |     await page.goto(BASE_URL);\n  776 |     await handleModeSelector(page, 'custodian');\n> 777 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  778 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/, { timeout: 4000 });\n  779 |     await page.locator('#tab-account').click();\n  780 |     await expect(page.locator('#ledger-account-panel')).toBeVisible();\n    at /var/www/360lm/tests/fundcustodian.spec.js:777:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:17:42.612Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-Accou-84a20-ers-for-admin-without-error-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-Accou-84a20-ers-for-admin-without-error-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-a4f9735155d5a1e85f73",
              "file": "fundcustodian.spec.js",
              "line": 773,
              "column": 3
            },
            {
              "title": "account running balance renders for admin without error",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1423,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30088,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 777
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  775 |     await page.goto(BASE_URL);\n  776 |     await handleModeSelector(page, 'custodian');\n> 777 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  778 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/, { timeout: 4000 });\n  779 |     await page.locator('#tab-account').click();\n  780 |     await expect(page.locator('#ledger-account-panel')).toBeVisible();\n    at /var/www/360lm/tests/fundcustodian.spec.js:777:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:16:56.628Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-Accou-84a20-ers-for-admin-without-error-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-Accou-84a20-ers-for-admin-without-error-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1424,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30092,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 39,
                            "line": 777
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-ledger')\u001b[22m\n\n\n  775 |     await page.goto(BASE_URL);\n  776 |     await handleModeSelector(page, 'custodian');\n> 777 |     await page.locator('#nav-ledger').click();\n      |                                       ^\n  778 |     await expect(page.locator('#s-ledger')).toHaveClass(/active/, { timeout: 4000 });\n  779 |     await page.locator('#tab-account').click();\n  780 |     await expect(page.locator('#ledger-account-panel')).toBeVisible();\n    at /var/www/360lm/tests/fundcustodian.spec.js:777:39"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:17:27.782Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-Accou-84a20-ers-for-admin-without-error-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Ledger-Accou-84a20-ers-for-admin-without-error-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-ae18f57980cef5e75d83",
              "file": "fundcustodian.spec.js",
              "line": 773,
              "column": 3
            }
          ]
        },
        {
          "title": "Onboarding Tour (custodian-v27)",
          "file": "fundcustodian.spec.js",
          "line": 790,
          "column": 6,
          "specs": [
            {
              "title": "? tour button present on dashboard",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 418,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10235,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:18:13.677Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-eb761-button-present-on-dashboard-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-eb761-button-present-on-dashboard-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    },
                    {
                      "workerIndex": 419,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10257,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:18:24.862Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-eb761-button-present-on-dashboard-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-eb761-button-present-on-dashboard-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-25798fc6357bb1c2fc8a",
              "file": "fundcustodian.spec.js",
              "line": 798,
              "column": 3
            },
            {
              "title": "tour overlay opens and shows 13 steps",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 420,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10257,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:18:36.131Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-b1106-ay-opens-and-shows-13-steps-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-b1106-ay-opens-and-shows-13-steps-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    },
                    {
                      "workerIndex": 421,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10257,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:18:47.396Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-b1106-ay-opens-and-shows-13-steps-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-b1106-ay-opens-and-shows-13-steps-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-02c7902ef9ca4db134ea",
              "file": "fundcustodian.spec.js",
              "line": 802,
              "column": 3
            },
            {
              "title": "tour navigates forward and back",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 422,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10303,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:18:58.708Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-6b930--navigates-forward-and-back-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-6b930--navigates-forward-and-back-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    },
                    {
                      "workerIndex": 423,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10314,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:19:10.000Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-6b930--navigates-forward-and-back-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-6b930--navigates-forward-and-back-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-34e83d1db5e27940f3a0",
              "file": "fundcustodian.spec.js",
              "line": 809,
              "column": 3
            },
            {
              "title": "tour skip button closes overlay",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 424,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10270,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:19:21.446Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-68591--skip-button-closes-overlay-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-68591--skip-button-closes-overlay-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    },
                    {
                      "workerIndex": 425,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10226,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:19:32.703Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-68591--skip-button-closes-overlay-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-68591--skip-button-closes-overlay-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-1abcb0430f8799670b9d",
              "file": "fundcustodian.spec.js",
              "line": 817,
              "column": 3
            },
            {
              "title": "SW cache version is current",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 426,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10268,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:19:43.895Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-8ddc9-SW-cache-version-is-current-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-8ddc9-SW-cache-version-is-current-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    },
                    {
                      "workerIndex": 427,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10302,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:19:55.172Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-8ddc9-SW-cache-version-is-current-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-8ddc9-SW-cache-version-is-current-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-a430ec68ed9fd52d7c6f",
              "file": "fundcustodian.spec.js",
              "line": 824,
              "column": 3
            },
            {
              "title": "? tour button present on dashboard",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1425,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10260,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:17:58.921Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-eb761-button-present-on-dashboard-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-eb761-button-present-on-dashboard-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    },
                    {
                      "workerIndex": 1426,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10283,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:18:10.325Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-eb761-button-present-on-dashboard-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-eb761-button-present-on-dashboard-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-0c403ea7abee2eac953e",
              "file": "fundcustodian.spec.js",
              "line": 798,
              "column": 3
            },
            {
              "title": "tour overlay opens and shows 13 steps",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1427,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10274,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:18:21.863Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-b1106-ay-opens-and-shows-13-steps-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-b1106-ay-opens-and-shows-13-steps-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    },
                    {
                      "workerIndex": 1428,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:18:33.358Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-b1106-ay-opens-and-shows-13-steps-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-b1106-ay-opens-and-shows-13-steps-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-8dc7176cba986382263e",
              "file": "fundcustodian.spec.js",
              "line": 802,
              "column": 3
            },
            {
              "title": "tour navigates forward and back",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1429,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10295,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:18:44.691Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-6b930--navigates-forward-and-back-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-6b930--navigates-forward-and-back-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    },
                    {
                      "workerIndex": 1430,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10276,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:18:56.162Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-6b930--navigates-forward-and-back-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-6b930--navigates-forward-and-back-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-748709e72f35803de34f",
              "file": "fundcustodian.spec.js",
              "line": 809,
              "column": 3
            },
            {
              "title": "tour skip button closes overlay",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1431,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10278,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:07.671Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-68591--skip-button-closes-overlay-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-68591--skip-button-closes-overlay-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    },
                    {
                      "workerIndex": 1432,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10264,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:19:19.008Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-68591--skip-button-closes-overlay-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-68591--skip-button-closes-overlay-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-9a86ff145ab87411e487",
              "file": "fundcustodian.spec.js",
              "line": 817,
              "column": 3
            },
            {
              "title": "SW cache version is current",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1433,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10268,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:30.414Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-8ddc9-SW-cache-version-is-current-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-8ddc9-SW-cache-version-is-current-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    },
                    {
                      "workerIndex": 1434,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10317,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47",
                        "location": {
                          "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                          "column": 47,
                          "line": 795
                        },
                        "snippet": "  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                            "column": 47,
                            "line": 795
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#my-balance')\nExpected substring: \u001b[32m\"₹\"\u001b[39m\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#my-balance')\u001b[22m\n\n\n  793 |     await injectSession(page, SESSION.shambhu);\n  794 |     await page.goto(BASE_URL);\n> 795 |     await expect(page.locator('#my-balance')).toContainText('₹', { timeout: 10000 });\n      |                                               ^\n  796 |   });\n  797 |\n  798 |   test('? tour button present on dashboard', async ({ page }) => {\n    at /var/www/360lm/tests/fundcustodian.spec.js:795:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:19:41.750Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-8ddc9-SW-cache-version-is-current-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/fundcustodian-Onboarding-T-8ddc9-SW-cache-version-is-current-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/fundcustodian.spec.js",
                        "column": 47,
                        "line": 795
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "1caa50027b536c34a40f-17e25a4477e8fdcea60e",
              "file": "fundcustodian.spec.js",
              "line": 824,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "health_smoke.spec.js",
      "file": "health_smoke.spec.js",
      "column": 0,
      "line": 0,
      "specs": [
        {
          "title": "register -> add -> RELOAD+UNLOCK -> admin UI cross-user view",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [
                {
                  "type": "skip",
                  "description": "Standalone Health Tracker — set HEALTH_TRACKER_URL env var to enable",
                  "location": {
                    "file": "/var/www/360lm/tests/health_smoke.spec.js",
                    "line": 6,
                    "column": 6
                  }
                }
              ],
              "expectedStatus": "skipped",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": -1,
                  "parallelIndex": -1,
                  "status": "skipped",
                  "duration": 0,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:20:05.759Z",
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Standalone Health Tracker — set HEALTH_TRACKER_URL env var to enable",
                      "location": {
                        "file": "/var/www/360lm/tests/health_smoke.spec.js",
                        "line": 6,
                        "column": 6
                      }
                    }
                  ],
                  "attachments": []
                }
              ],
              "status": "skipped"
            }
          ],
          "id": "b05376698a055fb8c1ba-01a9f03c303daa908eb4",
          "file": "health_smoke.spec.js",
          "line": 11,
          "column": 1
        },
        {
          "title": "register -> add -> RELOAD+UNLOCK -> admin UI cross-user view",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [
                {
                  "type": "skip",
                  "description": "Standalone Health Tracker — set HEALTH_TRACKER_URL env var to enable",
                  "location": {
                    "file": "/var/www/360lm/tests/health_smoke.spec.js",
                    "line": 6,
                    "column": 6
                  }
                }
              ],
              "expectedStatus": "skipped",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": -1,
                  "parallelIndex": -1,
                  "status": "skipped",
                  "duration": 0,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:19:52.315Z",
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Standalone Health Tracker — set HEALTH_TRACKER_URL env var to enable",
                      "location": {
                        "file": "/var/www/360lm/tests/health_smoke.spec.js",
                        "line": 6,
                        "column": 6
                      }
                    }
                  ],
                  "attachments": []
                }
              ],
              "status": "skipped"
            }
          ],
          "id": "b05376698a055fb8c1ba-407afa1f8256ff9879fb",
          "file": "health_smoke.spec.js",
          "line": 11,
          "column": 1
        }
      ]
    },
    {
      "title": "hr.spec.js",
      "file": "hr.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "HR Management — Access Control",
          "file": "hr.spec.js",
          "line": 17,
          "column": 6,
          "specs": [
            {
              "title": "no session → redirects to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 798,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:07.035Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-93f8841e1f49eb7621a4",
              "file": "hr.spec.js",
              "line": 18,
              "column": 3
            },
            {
              "title": "with valid session → home screen loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 425,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:08.092Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-a154d0175ca310e5fb89",
              "file": "hr.spec.js",
              "line": 23,
              "column": 3
            },
            {
              "title": "no session → redirects to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 688,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:53.128Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-98f1919840e230f3f0a8",
              "file": "hr.spec.js",
              "line": 18,
              "column": 3
            },
            {
              "title": "with valid session → home screen loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 401,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:53.988Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-d1caa549fb073e2fb62d",
              "file": "hr.spec.js",
              "line": 23,
              "column": 3
            }
          ]
        },
        {
          "title": "HR Management — User Bar: Topbar Screens",
          "file": "hr.spec.js",
          "line": 30,
          "column": 6,
          "specs": [
            {
              "title": "home topbar shows user name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 381,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:08.551Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-4dd8bc3fa551f6f2fc0b",
              "file": "hr.spec.js",
              "line": 35,
              "column": 3
            },
            {
              "title": "home topbar has Logout button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 386,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:08.964Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-0f0747cbdccb5743bbf5",
              "file": "hr.spec.js",
              "line": 40,
              "column": 3
            },
            {
              "title": "logout confirm=dismiss — stays on page",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 425,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:09.384Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-0efbece8363a26d7b996",
              "file": "hr.spec.js",
              "line": 45,
              "column": 3
            },
            {
              "title": "logout confirm=OK → redirects to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 667,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:09.829Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-93ab81e3991c7861b699",
              "file": "hr.spec.js",
              "line": 53,
              "column": 3
            },
            {
              "title": "home topbar shows user name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 392,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:54.416Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-7d3c1892ec14e8a7f204",
              "file": "hr.spec.js",
              "line": 35,
              "column": 3
            },
            {
              "title": "home topbar has Logout button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 383,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:54.849Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-6cf2e2d98d186fc640e2",
              "file": "hr.spec.js",
              "line": 40,
              "column": 3
            },
            {
              "title": "logout confirm=dismiss — stays on page",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 408,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:55.261Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-6723e85d05fa5b06bc6e",
              "file": "hr.spec.js",
              "line": 45,
              "column": 3
            },
            {
              "title": "logout confirm=OK → redirects to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 658,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:55.688Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-6bcb167a520971d057aa",
              "file": "hr.spec.js",
              "line": 53,
              "column": 3
            }
          ]
        },
        {
          "title": "HR Management — User Bar: No-Topbar Screens (injected)",
          "file": "hr.spec.js",
          "line": 62,
          "column": 6,
          "specs": [
            {
              "title": "scr-attendance gets a topbar prepended with Hub + Logout",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 501,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:10.521Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-7eec289a0151f0a85b50",
              "file": "hr.spec.js",
              "line": 67,
              "column": 3
            },
            {
              "title": "scr-attendance topbar Hub button navigates to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 718,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:11.052Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-1c46bc3e940f14e12a43",
              "file": "hr.spec.js",
              "line": 78,
              "column": 3
            },
            {
              "title": "scr-attendance gets a topbar prepended with Hub + Logout",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 577,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:56.376Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-f4a105b5880391714503",
              "file": "hr.spec.js",
              "line": 67,
              "column": 3
            },
            {
              "title": "scr-attendance topbar Hub button navigates to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 805,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:56.978Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-ed0ffc73d9c06bd3b6a1",
              "file": "hr.spec.js",
              "line": 78,
              "column": 3
            }
          ]
        },
        {
          "title": "HR Management — User Bar: Employee Role",
          "file": "hr.spec.js",
          "line": 88,
          "column": 6,
          "specs": [
            {
              "title": "employee home topbar shows their name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 311,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:11.790Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-df62e39b02edf42734c3",
              "file": "hr.spec.js",
              "line": 93,
              "column": 3
            },
            {
              "title": "employee home topbar has Logout button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 293,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:12.121Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-4882593ca53c335695b3",
              "file": "hr.spec.js",
              "line": 98,
              "column": 3
            },
            {
              "title": "employee home topbar shows their name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 389,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:57.801Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-acfff3368cdaf76cede0",
              "file": "hr.spec.js",
              "line": 93,
              "column": 3
            },
            {
              "title": "employee home topbar has Logout button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 325,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:58.213Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-9072c0c4372ea082848b",
              "file": "hr.spec.js",
              "line": 98,
              "column": 3
            }
          ]
        },
        {
          "title": "HR Management — Existing Functionality Unchanged",
          "file": "hr.spec.js",
          "line": 104,
          "column": 6,
          "specs": [
            {
              "title": "bottom nav renders correct items for admin",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 317,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:12.435Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-2ce28d43100e8a510f28",
              "file": "hr.spec.js",
              "line": 109,
              "column": 3
            },
            {
              "title": "home content area is populated",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 338,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:12.767Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-876327021cf36189da99",
              "file": "hr.spec.js",
              "line": 116,
              "column": 3
            },
            {
              "title": "existing Hub button still works on home topbar",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 428,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 374,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n\n    at /var/www/360lm/tests/hr.spec.js:126:62",
                        "location": {
                          "file": "/var/www/360lm/tests/hr.spec.js",
                          "column": 62,
                          "line": 126
                        },
                        "snippet": "  124 |     await page.waitForSelector('#scr-home.active', { timeout: 8000 });\n  125 |     // Both existing hub-btn and new logout-btn should coexist in topbar\n> 126 |     await expect(page.locator('#scr-home .topbar .hub-btn')).toBeVisible();\n      |                                                              ^\n  127 |   });\n  128 | });\n  129 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hr.spec.js",
                            "column": 62,
                            "line": 126
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n\n\n  124 |     await page.waitForSelector('#scr-home.active', { timeout: 8000 });\n  125 |     // Both existing hub-btn and new logout-btn should coexist in topbar\n> 126 |     await expect(page.locator('#scr-home .topbar .hub-btn')).toBeVisible();\n      |                                                              ^\n  127 |   });\n  128 | });\n  129 |\n    at /var/www/360lm/tests/hr.spec.js:126:62"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:13.126Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hr-HR-Management-—-Existin-521c3--still-works-on-home-topbar-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hr-HR-Management-—-Existin-521c3--still-works-on-home-topbar-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hr.spec.js",
                        "column": 62,
                        "line": 126
                      }
                    },
                    {
                      "workerIndex": 429,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 511,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n\n    at /var/www/360lm/tests/hr.spec.js:126:62",
                        "location": {
                          "file": "/var/www/360lm/tests/hr.spec.js",
                          "column": 62,
                          "line": 126
                        },
                        "snippet": "  124 |     await page.waitForSelector('#scr-home.active', { timeout: 8000 });\n  125 |     // Both existing hub-btn and new logout-btn should coexist in topbar\n> 126 |     await expect(page.locator('#scr-home .topbar .hub-btn')).toBeVisible();\n      |                                                              ^\n  127 |   });\n  128 | });\n  129 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hr.spec.js",
                            "column": 62,
                            "line": 126
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n\n\n  124 |     await page.waitForSelector('#scr-home.active', { timeout: 8000 });\n  125 |     // Both existing hub-btn and new logout-btn should coexist in topbar\n> 126 |     await expect(page.locator('#scr-home .topbar .hub-btn')).toBeVisible();\n      |                                                              ^\n  127 |   });\n  128 | });\n  129 |\n    at /var/www/360lm/tests/hr.spec.js:126:62"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:20:14.344Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hr-HR-Management-—-Existin-521c3--still-works-on-home-topbar-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hr-HR-Management-—-Existin-521c3--still-works-on-home-topbar-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hr.spec.js",
                        "column": 62,
                        "line": 126
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "98264c29bbadd0584463-0ad0523966278d674a7c",
              "file": "hr.spec.js",
              "line": 122,
              "column": 3
            },
            {
              "title": "bottom nav renders correct items for admin",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 346,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:58.558Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-5b5a17f7b4f7cbc4c543",
              "file": "hr.spec.js",
              "line": 109,
              "column": 3
            },
            {
              "title": "home content area is populated",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 334,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:58.930Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-ea2223456924354025c1",
              "file": "hr.spec.js",
              "line": 116,
              "column": 3
            },
            {
              "title": "existing Hub button still works on home topbar",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1435,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 390,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n\n    at /var/www/360lm/tests/hr.spec.js:126:62",
                        "location": {
                          "file": "/var/www/360lm/tests/hr.spec.js",
                          "column": 62,
                          "line": 126
                        },
                        "snippet": "  124 |     await page.waitForSelector('#scr-home.active', { timeout: 8000 });\n  125 |     // Both existing hub-btn and new logout-btn should coexist in topbar\n> 126 |     await expect(page.locator('#scr-home .topbar .hub-btn')).toBeVisible();\n      |                                                              ^\n  127 |   });\n  128 | });\n  129 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hr.spec.js",
                            "column": 62,
                            "line": 126
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n\n\n  124 |     await page.waitForSelector('#scr-home.active', { timeout: 8000 });\n  125 |     // Both existing hub-btn and new logout-btn should coexist in topbar\n> 126 |     await expect(page.locator('#scr-home .topbar .hub-btn')).toBeVisible();\n      |                                                              ^\n  127 |   });\n  128 | });\n  129 |\n    at /var/www/360lm/tests/hr.spec.js:126:62"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:19:59.285Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hr-HR-Management-—-Existin-521c3--still-works-on-home-topbar-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hr-HR-Management-—-Existin-521c3--still-works-on-home-topbar-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hr.spec.js",
                        "column": 62,
                        "line": 126
                      }
                    },
                    {
                      "workerIndex": 1436,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 901,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n\n    at /var/www/360lm/tests/hr.spec.js:126:62",
                        "location": {
                          "file": "/var/www/360lm/tests/hr.spec.js",
                          "column": 62,
                          "line": 126
                        },
                        "snippet": "  124 |     await page.waitForSelector('#scr-home.active', { timeout: 8000 });\n  125 |     // Both existing hub-btn and new logout-btn should coexist in topbar\n> 126 |     await expect(page.locator('#scr-home .topbar .hub-btn')).toBeVisible();\n      |                                                              ^\n  127 |   });\n  128 | });\n  129 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hr.spec.js",
                            "column": 62,
                            "line": 126
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#scr-home .topbar .hub-btn')\nExpected: visible\nError: strict mode violation: locator('#scr-home .topbar .hub-btn') resolved to 2 elements:\n    1) <button class=\"hub-btn\" title=\"Tutorials\" onclick=\"window.location.href='/learn/?pwa=hr'\">🎓 Tutorials</button> aka getByRole('button', { name: '🎓 Tutorials' })\n    2) <button class=\"hub-btn\" onclick=\"window.location.href='/hub/'\">🏡 Hub</button> aka getByRole('button', { name: '🏡 Hub' })\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#scr-home .topbar .hub-btn')\u001b[22m\n\n\n  124 |     await page.waitForSelector('#scr-home.active', { timeout: 8000 });\n  125 |     // Both existing hub-btn and new logout-btn should coexist in topbar\n> 126 |     await expect(page.locator('#scr-home .topbar .hub-btn')).toBeVisible();\n      |                                                              ^\n  127 |   });\n  128 | });\n  129 |\n    at /var/www/360lm/tests/hr.spec.js:126:62"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:20:00.680Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hr-HR-Management-—-Existin-521c3--still-works-on-home-topbar-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hr-HR-Management-—-Existin-521c3--still-works-on-home-topbar-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hr.spec.js",
                        "column": 62,
                        "line": 126
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "98264c29bbadd0584463-312c714c805cfe58aacf",
              "file": "hr.spec.js",
              "line": 122,
              "column": 3
            }
          ]
        },
        {
          "title": "HR Management — Salary Advance Recovery",
          "file": "hr.spec.js",
          "line": 137,
          "column": 6,
          "specs": [
            {
              "title": "get_employee_advances_for_salary RPC is reachable (hr profile)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 69,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:15.821Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-0e9178349aba126ae32d",
              "file": "hr.spec.js",
              "line": 138,
              "column": 3
            },
            {
              "title": "pay_salary_slip guards a non-existent slip (mutates nothing)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 27,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:15.938Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-b3b803d3fed16d1428bd",
              "file": "hr.spec.js",
              "line": 146,
              "column": 3
            },
            {
              "title": "pay modal markup includes advance-recovery section",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 474,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:15.995Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-a012dbe1416c4e1b261a",
              "file": "hr.spec.js",
              "line": 158,
              "column": 3
            },
            {
              "title": "service worker version is current",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 203,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:16.605Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-77c829ea4e6f07a517ae",
              "file": "hr.spec.js",
              "line": 168,
              "column": 3
            },
            {
              "title": "get_employee_advances_for_salary RPC is reachable (hr profile)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 142,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:04.111Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-c70fe4e5d35a47606af2",
              "file": "hr.spec.js",
              "line": 138,
              "column": 3
            },
            {
              "title": "pay_salary_slip guards a non-existent slip (mutates nothing)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 50,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:04.346Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-028fbd1b5548f7845057",
              "file": "hr.spec.js",
              "line": 146,
              "column": 3
            },
            {
              "title": "pay modal markup includes advance-recovery section",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 946,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:04.427Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-8039da32a6f33158e356",
              "file": "hr.spec.js",
              "line": 158,
              "column": 3
            },
            {
              "title": "service worker version is current",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 390,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:05.617Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-1812ca435c76bc65f915",
              "file": "hr.spec.js",
              "line": 168,
              "column": 3
            }
          ]
        },
        {
          "title": "HR Management — Employee read path (web_anon)",
          "file": "hr.spec.js",
          "line": 178,
          "column": 6,
          "specs": [
            {
              "title": "GET /db/employees (expense profile) returns rows for web_anon [BUG-1]",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:16.832Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-7621dadd18dd4b6b11e0",
              "file": "hr.spec.js",
              "line": 179,
              "column": 3
            },
            {
              "title": "generate_salary_slip runs as web_anon without permission error [BUG-3]",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 28,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:16.867Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-090e0d9032f49fc96ef7",
              "file": "hr.spec.js",
              "line": 189,
              "column": 3
            },
            {
              "title": "GET /db/employees (expense profile) returns rows for web_anon [BUG-1]",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 39,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:06.053Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-005ecd07d98551964620",
              "file": "hr.spec.js",
              "line": 179,
              "column": 3
            },
            {
              "title": "generate_salary_slip runs as web_anon without permission error [BUG-3]",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 50,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:06.122Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "98264c29bbadd0584463-e612f8208002c2c0a394",
              "file": "hr.spec.js",
              "line": 189,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "hub-reauth.spec.js",
      "file": "hub-reauth.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Hub — ?reauth=1 re-authorises uploads without logging the device out",
          "file": "hub-reauth.spec.js",
          "line": 49,
          "column": 6,
          "specs": [
            {
              "title": "SW cache version bumped",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 28,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:16.926Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-3ecfb4f2342bb5c64730",
              "file": "hub-reauth.spec.js",
              "line": 51,
              "column": 3
            },
            {
              "title": "a normal signed-in visit still goes straight to the home screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 496,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:16.968Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-f32fce6cc2feac1df904",
              "file": "hub-reauth.spec.js",
              "line": 56,
              "column": 3
            },
            {
              "title": "no session still shows the login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2199,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:17.477Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-83c639c96352a507b888",
              "file": "hub-reauth.spec.js",
              "line": 63,
              "column": 3
            },
            {
              "title": "?reauth=1 shows the PIN screen, explains why, and preselects the known employee",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 455,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:19.697Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-c2151a0747f6cf058c51",
              "file": "hub-reauth.spec.js",
              "line": 69,
              "column": 3
            },
            {
              "title": "one keypad tap enters exactly ONE digit (plain login)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4358,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:20.174Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-0f4108de147d4f26b4d0",
              "file": "hub-reauth.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "one keypad tap enters exactly ONE digit (?reauth=1)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2531,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:24.556Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-a4454f16450736940e0c",
              "file": "hub-reauth.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "?reauth=1 does NOT clear the canonical session",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1906,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:27.109Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-89154bfec5d6a19caf88",
              "file": "hub-reauth.spec.js",
              "line": 111,
              "column": 3
            },
            {
              "title": "SW cache version bumped",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 53,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:06.262Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-0adc01209372fde5a158",
              "file": "hub-reauth.spec.js",
              "line": 51,
              "column": 3
            },
            {
              "title": "a normal signed-in visit still goes straight to the home screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 895,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:06.351Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-81d4545a85d2bfbc19af",
              "file": "hub-reauth.spec.js",
              "line": 56,
              "column": 3
            },
            {
              "title": "no session still shows the login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2388,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:07.276Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-c07ebeeac76d4de14125",
              "file": "hub-reauth.spec.js",
              "line": 63,
              "column": 3
            },
            {
              "title": "?reauth=1 shows the PIN screen, explains why, and preselects the known employee",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 591,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:09.701Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-24ea2d3eb45a887161f5",
              "file": "hub-reauth.spec.js",
              "line": 69,
              "column": 3
            },
            {
              "title": "one keypad tap enters exactly ONE digit (plain login)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4424,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:10.323Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-30277c7407eb0a0cd99d",
              "file": "hub-reauth.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "one keypad tap enters exactly ONE digit (?reauth=1)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2571,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:14.764Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-4b134440f69cf57bb817",
              "file": "hub-reauth.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "?reauth=1 does NOT clear the canonical session",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1967,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:17.351Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-1ff50ce9d72b3124d524",
              "file": "hub-reauth.spec.js",
              "line": 111,
              "column": 3
            }
          ]
        },
        {
          "title": "Hub — the upload token is stored BEFORE the login navigates away (2026-08-26 loop)",
          "file": "hub-reauth.spec.js",
          "line": 128,
          "column": 6,
          "specs": [
            {
              "title": "a SLOW hub.login: PIN on ?reauth=1 still lands on imgbind WITH lm360-hub-refresh stored",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3313,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:29.029Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-4205280bab12ad9089f6",
              "file": "hub-reauth.spec.js",
              "line": 152,
              "column": 3
            },
            {
              "title": "hub.login FAILING on ?reauth=1 keeps the user on the Hub and says why (no silent bounce)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2969,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:32.576Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-538f2df10f1db57365ec",
              "file": "hub-reauth.spec.js",
              "line": 181,
              "column": 3
            },
            {
              "title": "a plain ?next= login is still NON-FATAL when hub.login fails — the user reaches the app",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4564,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:35.560Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-584ee5c1aab48d95ec81",
              "file": "hub-reauth.spec.js",
              "line": 203,
              "column": 3
            },
            {
              "title": "DIFFERENTIAL: the pre-fix v45 build loses the token when hub.login is slow",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "set HUB_DIFF=1 to run against /var/www/360lm/hub/index.html.bak-20260826-tokenrace",
                      "location": {
                        "file": "/var/www/360lm/tests/hub-reauth.spec.js",
                        "line": 223,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 116,
                      "errors": [],
                      "stdout": [
                        {
                          "text": "[cleanup] residue for zz_hub_spec (tokens|employees): 0|0\n"
                        }
                      ],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:40.145Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "set HUB_DIFF=1 to run against /var/www/360lm/hub/index.html.bak-20260826-tokenrace",
                          "location": {
                            "file": "/var/www/360lm/tests/hub-reauth.spec.js",
                            "line": 223,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "5b7471130318aee46252-bbc90481ca303dbbdfa7",
              "file": "hub-reauth.spec.js",
              "line": 222,
              "column": 3
            },
            {
              "title": "a SLOW hub.login: PIN on ?reauth=1 still lands on imgbind WITH lm360-hub-refresh stored",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3337,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:19.346Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-b5e0ef2f46eee111d057",
              "file": "hub-reauth.spec.js",
              "line": 152,
              "column": 3
            },
            {
              "title": "hub.login FAILING on ?reauth=1 keeps the user on the Hub and says why (no silent bounce)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2984,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:22.881Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-30e11663916246c01ba3",
              "file": "hub-reauth.spec.js",
              "line": 181,
              "column": 3
            },
            {
              "title": "a plain ?next= login is still NON-FATAL when hub.login fails — the user reaches the app",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4630,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:25.879Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "5b7471130318aee46252-32d1f3265603ebeee0bb",
              "file": "hub-reauth.spec.js",
              "line": 203,
              "column": 3
            },
            {
              "title": "DIFFERENTIAL: the pre-fix v45 build loses the token when hub.login is slow",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "set HUB_DIFF=1 to run against /var/www/360lm/hub/index.html.bak-20260826-tokenrace",
                      "location": {
                        "file": "/var/www/360lm/tests/hub-reauth.spec.js",
                        "line": 223,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 127,
                      "errors": [],
                      "stdout": [
                        {
                          "text": "[cleanup] residue for zz_hub_spec (tokens|employees): 0|0\n"
                        }
                      ],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:30.523Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "set HUB_DIFF=1 to run against /var/www/360lm/hub/index.html.bak-20260826-tokenrace",
                          "location": {
                            "file": "/var/www/360lm/tests/hub-reauth.spec.js",
                            "line": 223,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "5b7471130318aee46252-57bdf757cadfff2cf5b7",
              "file": "hub-reauth.spec.js",
              "line": 222,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "hub.spec.js",
      "file": "hub.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Hub — Page Load",
          "file": "hub.spec.js",
          "line": 16,
          "column": 6,
          "specs": [
            {
              "title": "without session → shows login screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 430,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8147,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:19:56",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 56,
                          "line": 19
                        },
                        "snippet": "  17 |   test('without session → shows login screen', async ({ page }) => {\n  18 |     await page.goto(BASE_URL);\n> 19 |     await expect(page.locator('#screen-login.active')).toBeVisible({ timeout: 8000 });\n     |                                                        ^\n  20 |   });\n  21 |\n  22 |   test('manifest.json reachable', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 56,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n\n\n  17 |   test('without session → shows login screen', async ({ page }) => {\n  18 |     await page.goto(BASE_URL);\n> 19 |     await expect(page.locator('#screen-login.active')).toBeVisible({ timeout: 8000 });\n     |                                                        ^\n  20 |   });\n  21 |\n  22 |   test('manifest.json reachable', async ({ page }) => {\n    at /var/www/360lm/tests/hub.spec.js:19:56"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:40.684Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-without-session-→-shows-login-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-without-session-→-shows-login-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 56,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 431,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8224,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:19:56",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 56,
                          "line": 19
                        },
                        "snippet": "  17 |   test('without session → shows login screen', async ({ page }) => {\n  18 |     await page.goto(BASE_URL);\n> 19 |     await expect(page.locator('#screen-login.active')).toBeVisible({ timeout: 8000 });\n     |                                                        ^\n  20 |   });\n  21 |\n  22 |   test('manifest.json reachable', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 56,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n\n\n  17 |   test('without session → shows login screen', async ({ page }) => {\n  18 |     await page.goto(BASE_URL);\n> 19 |     await expect(page.locator('#screen-login.active')).toBeVisible({ timeout: 8000 });\n     |                                                        ^\n  20 |   });\n  21 |\n  22 |   test('manifest.json reachable', async ({ page }) => {\n    at /var/www/360lm/tests/hub.spec.js:19:56"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:20:49.632Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-without-session-→-shows-login-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-without-session-→-shows-login-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 56,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-b768f3286456e5f548af",
              "file": "hub.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "manifest.json reachable",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 432,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 315,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/hub.spec.js:24:24",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 24,
                          "line": 24
                        },
                        "snippet": "  22 |   test('manifest.json reachable', async ({ page }) => {\n  23 |     const r = await page.request.get(BASE_URL + 'manifest.json');\n> 24 |     expect(r.status()).toBe(200);\n     |                        ^\n  25 |     const j = await r.json();\n  26 |     expect(j.name).toBeTruthy();\n  27 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 24,
                            "line": 24
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  22 |   test('manifest.json reachable', async ({ page }) => {\n  23 |     const r = await page.request.get(BASE_URL + 'manifest.json');\n> 24 |     expect(r.status()).toBe(200);\n     |                        ^\n  25 |     const j = await r.json();\n  26 |     expect(j.name).toBeTruthy();\n  27 |   });\n    at /var/www/360lm/tests/hub.spec.js:24:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:20:58.806Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-manifest-json-reachable-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-manifest-json-reachable-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 24,
                        "line": 24
                      }
                    },
                    {
                      "workerIndex": 433,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 312,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/hub.spec.js:24:24",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 24,
                          "line": 24
                        },
                        "snippet": "  22 |   test('manifest.json reachable', async ({ page }) => {\n  23 |     const r = await page.request.get(BASE_URL + 'manifest.json');\n> 24 |     expect(r.status()).toBe(200);\n     |                        ^\n  25 |     const j = await r.json();\n  26 |     expect(j.name).toBeTruthy();\n  27 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 24,
                            "line": 24
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  22 |   test('manifest.json reachable', async ({ page }) => {\n  23 |     const r = await page.request.get(BASE_URL + 'manifest.json');\n> 24 |     expect(r.status()).toBe(200);\n     |                        ^\n  25 |     const j = await r.json();\n  26 |     expect(j.name).toBeTruthy();\n  27 |   });\n    at /var/www/360lm/tests/hub.spec.js:24:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:21:00.116Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-manifest-json-reachable-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-manifest-json-reachable-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 24,
                        "line": 24
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-5b3d816af28408cd3400",
              "file": "hub.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "with session → home screen shown",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 434,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10393,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:32:55",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 55,
                          "line": 32
                        },
                        "snippet": "  30 |     await injectSession(page);\n  31 |     await page.goto(BASE_URL);\n> 32 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 10000 });\n     |                                                       ^\n  33 |   });\n  34 | });\n  35 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 55,
                            "line": 32
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  30 |     await injectSession(page);\n  31 |     await page.goto(BASE_URL);\n> 32 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 10000 });\n     |                                                       ^\n  33 |   });\n  34 | });\n  35 |\n    at /var/www/360lm/tests/hub.spec.js:32:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:21:01.607Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-with-session-→-home-screen-shown-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-with-session-→-home-screen-shown-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 55,
                        "line": 32
                      }
                    },
                    {
                      "workerIndex": 435,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10238,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:32:55",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 55,
                          "line": 32
                        },
                        "snippet": "  30 |     await injectSession(page);\n  31 |     await page.goto(BASE_URL);\n> 32 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 10000 });\n     |                                                       ^\n  33 |   });\n  34 | });\n  35 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 55,
                            "line": 32
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  30 |     await injectSession(page);\n  31 |     await page.goto(BASE_URL);\n> 32 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 10000 });\n     |                                                       ^\n  33 |   });\n  34 | });\n  35 |\n    at /var/www/360lm/tests/hub.spec.js:32:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:21:13.129Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-with-session-→-home-screen-shown-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-with-session-→-home-screen-shown-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 55,
                        "line": 32
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-262ee9cd139e6540a0bb",
              "file": "hub.spec.js",
              "line": 29,
              "column": 3
            },
            {
              "title": "without session → shows login screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1437,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8200,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:19:56",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 56,
                          "line": 19
                        },
                        "snippet": "  17 |   test('without session → shows login screen', async ({ page }) => {\n  18 |     await page.goto(BASE_URL);\n> 19 |     await expect(page.locator('#screen-login.active')).toBeVisible({ timeout: 8000 });\n     |                                                        ^\n  20 |   });\n  21 |\n  22 |   test('manifest.json reachable', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 56,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n\n\n  17 |   test('without session → shows login screen', async ({ page }) => {\n  18 |     await page.goto(BASE_URL);\n> 19 |     await expect(page.locator('#screen-login.active')).toBeVisible({ timeout: 8000 });\n     |                                                        ^\n  20 |   });\n  21 |\n  22 |   test('manifest.json reachable', async ({ page }) => {\n    at /var/www/360lm/tests/hub.spec.js:19:56"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:31.056Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-without-session-→-shows-login-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-without-session-→-shows-login-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 56,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 1438,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8296,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:19:56",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 56,
                          "line": 19
                        },
                        "snippet": "  17 |   test('without session → shows login screen', async ({ page }) => {\n  18 |     await page.goto(BASE_URL);\n> 19 |     await expect(page.locator('#screen-login.active')).toBeVisible({ timeout: 8000 });\n     |                                                        ^\n  20 |   });\n  21 |\n  22 |   test('manifest.json reachable', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 56,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login.active')\u001b[22m\n\n\n  17 |   test('without session → shows login screen', async ({ page }) => {\n  18 |     await page.goto(BASE_URL);\n> 19 |     await expect(page.locator('#screen-login.active')).toBeVisible({ timeout: 8000 });\n     |                                                        ^\n  20 |   });\n  21 |\n  22 |   test('manifest.json reachable', async ({ page }) => {\n    at /var/www/360lm/tests/hub.spec.js:19:56"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:20:40.192Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-without-session-→-shows-login-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-without-session-→-shows-login-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 56,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-ac45d0f9116982d477a8",
              "file": "hub.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "manifest.json reachable",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1439,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 316,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/hub.spec.js:24:24",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 24,
                          "line": 24
                        },
                        "snippet": "  22 |   test('manifest.json reachable', async ({ page }) => {\n  23 |     const r = await page.request.get(BASE_URL + 'manifest.json');\n> 24 |     expect(r.status()).toBe(200);\n     |                        ^\n  25 |     const j = await r.json();\n  26 |     expect(j.name).toBeTruthy();\n  27 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 24,
                            "line": 24
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  22 |   test('manifest.json reachable', async ({ page }) => {\n  23 |     const r = await page.request.get(BASE_URL + 'manifest.json');\n> 24 |     expect(r.status()).toBe(200);\n     |                        ^\n  25 |     const j = await r.json();\n  26 |     expect(j.name).toBeTruthy();\n  27 |   });\n    at /var/www/360lm/tests/hub.spec.js:24:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:49.563Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-manifest-json-reachable-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-manifest-json-reachable-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 24,
                        "line": 24
                      }
                    },
                    {
                      "workerIndex": 1440,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 363,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/hub.spec.js:24:24",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 24,
                          "line": 24
                        },
                        "snippet": "  22 |   test('manifest.json reachable', async ({ page }) => {\n  23 |     const r = await page.request.get(BASE_URL + 'manifest.json');\n> 24 |     expect(r.status()).toBe(200);\n     |                        ^\n  25 |     const j = await r.json();\n  26 |     expect(j.name).toBeTruthy();\n  27 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 24,
                            "line": 24
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  22 |   test('manifest.json reachable', async ({ page }) => {\n  23 |     const r = await page.request.get(BASE_URL + 'manifest.json');\n> 24 |     expect(r.status()).toBe(200);\n     |                        ^\n  25 |     const j = await r.json();\n  26 |     expect(j.name).toBeTruthy();\n  27 |   });\n    at /var/www/360lm/tests/hub.spec.js:24:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:20:50.944Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-manifest-json-reachable-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-manifest-json-reachable-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 24,
                        "line": 24
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-1ed0182f327ded697068",
              "file": "hub.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "with session → home screen shown",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1441,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10335,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:32:55",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 55,
                          "line": 32
                        },
                        "snippet": "  30 |     await injectSession(page);\n  31 |     await page.goto(BASE_URL);\n> 32 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 10000 });\n     |                                                       ^\n  33 |   });\n  34 | });\n  35 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 55,
                            "line": 32
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  30 |     await injectSession(page);\n  31 |     await page.goto(BASE_URL);\n> 32 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 10000 });\n     |                                                       ^\n  33 |   });\n  34 | });\n  35 |\n    at /var/www/360lm/tests/hub.spec.js:32:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:20:52.375Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-with-session-→-home-screen-shown-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-with-session-→-home-screen-shown-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 55,
                        "line": 32
                      }
                    },
                    {
                      "workerIndex": 1442,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:32:55",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 55,
                          "line": 32
                        },
                        "snippet": "  30 |     await injectSession(page);\n  31 |     await page.goto(BASE_URL);\n> 32 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 10000 });\n     |                                                       ^\n  33 |   });\n  34 | });\n  35 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 55,
                            "line": 32
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 10000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 10000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  30 |     await injectSession(page);\n  31 |     await page.goto(BASE_URL);\n> 32 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 10000 });\n     |                                                       ^\n  33 |   });\n  34 | });\n  35 |\n    at /var/www/360lm/tests/hub.spec.js:32:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:21:04.236Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-with-session-→-home-screen-shown-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Page-Load-with-session-→-home-screen-shown-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 55,
                        "line": 32
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-079cf939f49276b023ba",
              "file": "hub.spec.js",
              "line": 29,
              "column": 3
            }
          ]
        },
        {
          "title": "Hub — Dashboard KPIs (management)",
          "file": "hub.spec.js",
          "line": 40,
          "column": 6,
          "specs": [
            {
              "title": "all KPI sections render",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 436,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10237,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:21:24.293Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-management-all-KPI-sections-render-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-management-all-KPI-sections-render-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 437,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10251,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:21:35.583Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-management-all-KPI-sections-render-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-management-all-KPI-sections-render-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-7717339074da37c47623",
              "file": "hub.spec.js",
              "line": 50,
              "column": 3
            },
            {
              "title": "Printing KPI section has 3 cards",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 438,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10248,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:21:46.739Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-54525-ing-KPI-section-has-3-cards-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-54525-ing-KPI-section-has-3-cards-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 439,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10262,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:21:57.932Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-54525-ing-KPI-section-has-3-cards-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-54525-ing-KPI-section-has-3-cards-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-66680c6cdd7dd9a87bf8",
              "file": "hub.spec.js",
              "line": 59,
              "column": 3
            },
            {
              "title": "Printing KPI cards link to /printing/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 440,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10230,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:22:09.152Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-fc1dd-KPI-cards-link-to-printing--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-fc1dd-KPI-cards-link-to-printing--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 441,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10248,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:22:20.430Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-fc1dd-KPI-cards-link-to-printing--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-fc1dd-KPI-cards-link-to-printing--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-fee0dc438774a12593f7",
              "file": "hub.spec.js",
              "line": 64,
              "column": 3
            },
            {
              "title": "Printing KPI labels are correct",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 442,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10252,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:22:31.628Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-760e6-ting-KPI-labels-are-correct-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-760e6-ting-KPI-labels-are-correct-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 443,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10261,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:22:42.870Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-760e6-ting-KPI-labels-are-correct-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-760e6-ting-KPI-labels-are-correct-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-65178d7fa19b21c6fdd6",
              "file": "hub.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "Printing KPI section title is present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 444,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10272,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:22:54.064Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-3f5e5-PI-section-title-is-present-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-3f5e5-PI-section-title-is-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 445,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10248,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:23:05.524Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-3f5e5-PI-section-title-is-present-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-3f5e5-PI-section-title-is-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-fce528c4d7cafa72cad8",
              "file": "hub.spec.js",
              "line": 83,
              "column": 3
            },
            {
              "title": "all KPI sections render",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1443,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10253,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:21:15.555Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-management-all-KPI-sections-render-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-management-all-KPI-sections-render-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 1444,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10259,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:21:26.790Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-management-all-KPI-sections-render-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-management-all-KPI-sections-render-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-0209beb9c3e548fe4c88",
              "file": "hub.spec.js",
              "line": 50,
              "column": 3
            },
            {
              "title": "Printing KPI section has 3 cards",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1445,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10260,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:21:38.097Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-54525-ing-KPI-section-has-3-cards-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-54525-ing-KPI-section-has-3-cards-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 1446,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10276,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:21:49.458Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-54525-ing-KPI-section-has-3-cards-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-54525-ing-KPI-section-has-3-cards-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-5764318d4a21647a3b22",
              "file": "hub.spec.js",
              "line": 59,
              "column": 3
            },
            {
              "title": "Printing KPI cards link to /printing/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1447,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10371,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:22:01.013Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-fc1dd-KPI-cards-link-to-printing--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-fc1dd-KPI-cards-link-to-printing--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 1448,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10230,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:22:12.385Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-fc1dd-KPI-cards-link-to-printing--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-fc1dd-KPI-cards-link-to-printing--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-4db9325e61e3b98af984",
              "file": "hub.spec.js",
              "line": 64,
              "column": 3
            },
            {
              "title": "Printing KPI labels are correct",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1449,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10253,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:22:23.659Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-760e6-ting-KPI-labels-are-correct-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-760e6-ting-KPI-labels-are-correct-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 1450,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10264,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:22:34.971Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-760e6-ting-KPI-labels-are-correct-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-760e6-ting-KPI-labels-are-correct-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-c09ef9467ec5de2f2c81",
              "file": "hub.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "Printing KPI section title is present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1451,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10233,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:22:46.280Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-3f5e5-PI-section-title-is-present-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-3f5e5-PI-section-title-is-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    },
                    {
                      "workerIndex": 1452,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10269,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:44:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 44
                        },
                        "snippet": "  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 44
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  42 |     await injectSession(page, 'harish', 'Harish');\n  43 |     await page.goto(BASE_URL);\n> 44 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  45 |     // Click the Dashboard tile to enter the KPI view\n  46 |     await page.locator('#hub-tile-dash').click();\n  47 |     await page.waitForSelector('#kpi-actions', { timeout: 5000 });\n    at /var/www/360lm/tests/hub.spec.js:44:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:22:57.556Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-3f5e5-PI-section-title-is-present-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Dashboard-KPIs-m-3f5e5-PI-section-title-is-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 44
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-aaa4cd640ffa87b58792",
              "file": "hub.spec.js",
              "line": 83,
              "column": 3
            }
          ]
        },
        {
          "title": "Hub — Print Nest tile (management via All Tools)",
          "file": "hub.spec.js",
          "line": 92,
          "column": 6,
          "specs": [
            {
              "title": "Print Nest tile is visible",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 446,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10255,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:96:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 96
                        },
                        "snippet": "  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 96
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });\n    at /var/www/360lm/tests/hub.spec.js:96:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:23:16.808Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--29c60--Print-Nest-tile-is-visible-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--29c60--Print-Nest-tile-is-visible-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 96
                      }
                    },
                    {
                      "workerIndex": 447,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10256,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:96:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 96
                        },
                        "snippet": "  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 96
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });\n    at /var/www/360lm/tests/hub.spec.js:96:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:23:28.012Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--29c60--Print-Nest-tile-is-visible-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--29c60--Print-Nest-tile-is-visible-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 96
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-36691724a6d90e7b4fd3",
              "file": "hub.spec.js",
              "line": 101,
              "column": 3
            },
            {
              "title": "Print Nest tile navigates to /printing/ on click",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 448,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10242,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:96:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 96
                        },
                        "snippet": "  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 96
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });\n    at /var/www/360lm/tests/hub.spec.js:96:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:23:39.238Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--80124-igates-to-printing-on-click-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--80124-igates-to-printing-on-click-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 96
                      }
                    },
                    {
                      "workerIndex": 449,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10233,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:96:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 96
                        },
                        "snippet": "  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 96
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });\n    at /var/www/360lm/tests/hub.spec.js:96:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:23:50.542Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--80124-igates-to-printing-on-click-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--80124-igates-to-printing-on-click-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 96
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-9f3fef950e7c86a271ac",
              "file": "hub.spec.js",
              "line": 105,
              "column": 3
            },
            {
              "title": "Print Nest tile is visible",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1453,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10260,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:96:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 96
                        },
                        "snippet": "  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 96
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });\n    at /var/www/360lm/tests/hub.spec.js:96:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:23:08.961Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--29c60--Print-Nest-tile-is-visible-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--29c60--Print-Nest-tile-is-visible-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 96
                      }
                    },
                    {
                      "workerIndex": 1454,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10234,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:96:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 96
                        },
                        "snippet": "  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 96
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });\n    at /var/www/360lm/tests/hub.spec.js:96:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:23:20.270Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--29c60--Print-Nest-tile-is-visible-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--29c60--Print-Nest-tile-is-visible-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 96
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-dbe8ac8fc315676ba731",
              "file": "hub.spec.js",
              "line": 101,
              "column": 3
            },
            {
              "title": "Print Nest tile navigates to /printing/ on click",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1455,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10271,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:96:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 96
                        },
                        "snippet": "  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 96
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });\n    at /var/www/360lm/tests/hub.spec.js:96:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:23:31.489Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--80124-igates-to-printing-on-click-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--80124-igates-to-printing-on-click-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 96
                      }
                    },
                    {
                      "workerIndex": 1456,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10247,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:96:16",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 16,
                          "line": 96
                        },
                        "snippet": "  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 16,
                            "line": 96
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  94 |     await injectSession(page, 'harish', 'Harish');\n  95 |     await page.goto(BASE_URL);\n> 96 |     await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n     |                ^\n  97 |     await page.locator('#hub-tile-tools').click();\n  98 |     await page.waitForSelector('#tile-grid', { timeout: 5000 });\n  99 |   });\n    at /var/www/360lm/tests/hub.spec.js:96:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:23:42.804Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--80124-igates-to-printing-on-click-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Print-Nest-tile--80124-igates-to-printing-on-click-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 16,
                        "line": 96
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-5bc46458cffeb9f56654",
              "file": "hub.spec.js",
              "line": 105,
              "column": 3
            }
          ]
        },
        {
          "title": "Hub — Field staff printing access",
          "file": "hub.spec.js",
          "line": 115,
          "column": 6,
          "specs": [
            {
              "title": "rakesh sees Print Nest tile on home",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 450,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10376,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });\n    at /var/www/360lm/tests/hub.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:24:02.289Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-5a569-ees-Print-Nest-tile-on-home-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-5a569-ees-Print-Nest-tile-on-home-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    },
                    {
                      "workerIndex": 451,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10275,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });\n    at /var/www/360lm/tests/hub.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:24:13.750Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-5a569-ees-Print-Nest-tile-on-home-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-5a569-ees-Print-Nest-tile-on-home-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-ed60e293819d95ed8e82",
              "file": "hub.spec.js",
              "line": 120,
              "column": 5
            },
            {
              "title": "ashok sees Print Nest tile on home",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 452,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10236,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });\n    at /var/www/360lm/tests/hub.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:24:24.969Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-2e5fc-ees-Print-Nest-tile-on-home-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-2e5fc-ees-Print-Nest-tile-on-home-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    },
                    {
                      "workerIndex": 453,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10223,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });\n    at /var/www/360lm/tests/hub.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:24:36.152Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-2e5fc-ees-Print-Nest-tile-on-home-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-2e5fc-ees-Print-Nest-tile-on-home-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-6380a1ecd435820f741a",
              "file": "hub.spec.js",
              "line": 120,
              "column": 5
            },
            {
              "title": "shambhu does not see Print Nest tile",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 454,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10237,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:24:47.298Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-f0520-oes-not-see-Print-Nest-tile-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-f0520-oes-not-see-Print-Nest-tile-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    },
                    {
                      "workerIndex": 455,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10221,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:24:58.500Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-f0520-oes-not-see-Print-Nest-tile-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-f0520-oes-not-see-Print-Nest-tile-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-69950b13bfbbdd638553",
              "file": "hub.spec.js",
              "line": 130,
              "column": 5
            },
            {
              "title": "gurpreet does not see Print Nest tile",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 456,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10238,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:25:09.637Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-bdf73-oes-not-see-Print-Nest-tile-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-bdf73-oes-not-see-Print-Nest-tile-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    },
                    {
                      "workerIndex": 457,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10263,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:25:20.823Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-bdf73-oes-not-see-Print-Nest-tile-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-bdf73-oes-not-see-Print-Nest-tile-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-145338cce41a6c8e5d13",
              "file": "hub.spec.js",
              "line": 130,
              "column": 5
            },
            {
              "title": "sachin does not see Print Nest tile",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 458,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10222,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:25:32.075Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-a90f9-oes-not-see-Print-Nest-tile-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-a90f9-oes-not-see-Print-Nest-tile-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    },
                    {
                      "workerIndex": 459,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10246,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:25:43.257Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-a90f9-oes-not-see-Print-Nest-tile-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-a90f9-oes-not-see-Print-Nest-tile-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-dedc033b7ff0c802edc2",
              "file": "hub.spec.js",
              "line": 130,
              "column": 5
            },
            {
              "title": "rakesh sees Print Nest tile on home",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1457,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10281,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });\n    at /var/www/360lm/tests/hub.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:23:54.116Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-5a569-ees-Print-Nest-tile-on-home-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-5a569-ees-Print-Nest-tile-on-home-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    },
                    {
                      "workerIndex": 1458,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10301,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });\n    at /var/www/360lm/tests/hub.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:24:05.655Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-5a569-ees-Print-Nest-tile-on-home-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-5a569-ees-Print-Nest-tile-on-home-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-67a57dc75ca1ec1fcc05",
              "file": "hub.spec.js",
              "line": 120,
              "column": 5
            },
            {
              "title": "ashok sees Print Nest tile on home",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1459,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10252,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });\n    at /var/www/360lm/tests/hub.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:24:16.956Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-2e5fc-ees-Print-Nest-tile-on-home-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-2e5fc-ees-Print-Nest-tile-on-home-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    },
                    {
                      "workerIndex": 1460,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10237,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  121 |       await injectSession(page, empId, empId);\n  122 |       await page.goto(BASE_URL);\n> 123 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  124 |       await page.waitForTimeout(2000);\n  125 |       await expect(page.locator('text=Print Nest').first()).toBeVisible({ timeout: 5000 });\n  126 |     });\n    at /var/www/360lm/tests/hub.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:24:28.232Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-2e5fc-ees-Print-Nest-tile-on-home-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-2e5fc-ees-Print-Nest-tile-on-home-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-f297caa17a9ab77ab125",
              "file": "hub.spec.js",
              "line": 120,
              "column": 5
            },
            {
              "title": "shambhu does not see Print Nest tile",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1461,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10258,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:24:39.493Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-f0520-oes-not-see-Print-Nest-tile-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-f0520-oes-not-see-Print-Nest-tile-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    },
                    {
                      "workerIndex": 1462,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10243,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:24:50.741Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-f0520-oes-not-see-Print-Nest-tile-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-f0520-oes-not-see-Print-Nest-tile-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-25ecbfc385db981e561a",
              "file": "hub.spec.js",
              "line": 130,
              "column": 5
            },
            {
              "title": "gurpreet does not see Print Nest tile",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1463,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10454,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:25:02.808Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-bdf73-oes-not-see-Print-Nest-tile-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-bdf73-oes-not-see-Print-Nest-tile-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    },
                    {
                      "workerIndex": 1464,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10241,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:25:14.443Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-bdf73-oes-not-see-Print-Nest-tile-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-bdf73-oes-not-see-Print-Nest-tile-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-ac3d083f3ca01352cea1",
              "file": "hub.spec.js",
              "line": 130,
              "column": 5
            },
            {
              "title": "sachin does not see Print Nest tile",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1465,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10255,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:25:25.709Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-a90f9-oes-not-see-Print-Nest-tile-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-a90f9-oes-not-see-Print-Nest-tile-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    },
                    {
                      "workerIndex": 1466,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10261,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/hub.spec.js:133:18",
                        "location": {
                          "file": "/var/www/360lm/tests/hub.spec.js",
                          "column": 18,
                          "line": 133
                        },
                        "snippet": "  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/hub.spec.js",
                            "column": 18,
                            "line": 133
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 10000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  131 |       await injectSession(page, empId, empId);\n  132 |       await page.goto(BASE_URL);\n> 133 |       await page.waitForSelector('#screen-home.active', { timeout: 10000 });\n      |                  ^\n  134 |       await page.waitForTimeout(2000);\n  135 |       const count = await page.locator('text=Print Nest').count();\n  136 |       expect(count).toBe(0);\n    at /var/www/360lm/tests/hub.spec.js:133:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:25:36.998Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-a90f9-oes-not-see-Print-Nest-tile-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/hub-Hub-—-Field-staff-prin-a90f9-oes-not-see-Print-Nest-tile-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/hub.spec.js",
                        "column": 18,
                        "line": 133
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "b3b9a722f7fa55cbef8a-967649a5b0160c15db3c",
              "file": "hub.spec.js",
              "line": 130,
              "column": 5
            }
          ]
        }
      ]
    },
    {
      "title": "imgbind-upload-retry.spec.js",
      "file": "imgbind-upload-retry.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "ImageBinding — the upload outbox is retryable and its failure is visible",
          "file": "imgbind-upload-retry.spec.js",
          "line": 64,
          "column": 6,
          "specs": [
            {
              "title": "SW cache version matches CACHE_VER",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 460,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 72,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:25:54.449Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "468423354f751d04c703-8bac1df57eab2e4d504c",
              "file": "imgbind-upload-retry.spec.js",
              "line": 66,
              "column": 3
            },
            {
              "title": "the refresh button RETRIES a held upload (it used to only pull server state)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 460,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 7158,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:25:54.561Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "468423354f751d04c703-d06b193a1efcb32488a4",
              "file": "imgbind-upload-retry.spec.js",
              "line": 74,
              "column": 3
            },
            {
              "title": "refresh does NOT claim \"Synced\" while a photo is still stuck",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 460,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6758,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:01.875Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "468423354f751d04c703-cfbf2947f103fa1b0476",
              "file": "imgbind-upload-retry.spec.js",
              "line": 97,
              "column": 3
            },
            {
              "title": "a photo held with no upload token says so ON THE CHIP, and offers a way out",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 460,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5412,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:08.681Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "468423354f751d04c703-9ea9bcb22e44402b396e",
              "file": "imgbind-upload-retry.spec.js",
              "line": 118,
              "column": 3
            },
            {
              "title": "with a valid token, refresh uploads the held photo all the way to the server",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "SPEC_HUB_TOKEN not provided",
                      "location": {
                        "file": "/var/www/360lm/tests/imgbind-upload-retry.spec.js",
                        "line": 143,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 460,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 116,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:14.123Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "SPEC_HUB_TOKEN not provided",
                          "location": {
                            "file": "/var/www/360lm/tests/imgbind-upload-retry.spec.js",
                            "line": 143,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "468423354f751d04c703-86c3ee7ead9207e11279",
              "file": "imgbind-upload-retry.spec.js",
              "line": 140,
              "column": 3
            },
            {
              "title": "SW cache version matches CACHE_VER",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1467,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 84,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:25:48.237Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "468423354f751d04c703-2c99090ea02c51b6659c",
              "file": "imgbind-upload-retry.spec.js",
              "line": 66,
              "column": 3
            },
            {
              "title": "the refresh button RETRIES a held upload (it used to only pull server state)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1467,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 7099,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:25:48.363Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "468423354f751d04c703-625648ed9e4d28684367",
              "file": "imgbind-upload-retry.spec.js",
              "line": 74,
              "column": 3
            },
            {
              "title": "refresh does NOT claim \"Synced\" while a photo is still stuck",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1467,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6571,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:25:55.583Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "468423354f751d04c703-0c46cbce5801d28a7a71",
              "file": "imgbind-upload-retry.spec.js",
              "line": 97,
              "column": 3
            },
            {
              "title": "a photo held with no upload token says so ON THE CHIP, and offers a way out",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1467,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5601,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:02.182Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "468423354f751d04c703-2968bb85cf903a4249b7",
              "file": "imgbind-upload-retry.spec.js",
              "line": 118,
              "column": 3
            },
            {
              "title": "with a valid token, refresh uploads the held photo all the way to the server",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "SPEC_HUB_TOKEN not provided",
                      "location": {
                        "file": "/var/www/360lm/tests/imgbind-upload-retry.spec.js",
                        "line": 143,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1467,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 124,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:07.812Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "SPEC_HUB_TOKEN not provided",
                          "location": {
                            "file": "/var/www/360lm/tests/imgbind-upload-retry.spec.js",
                            "line": 143,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "468423354f751d04c703-59f7d7db5796972c7a2d",
              "file": "imgbind-upload-retry.spec.js",
              "line": 140,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "installation.spec.js",
      "file": "installation.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Installation — Smoke",
          "file": "installation.spec.js",
          "line": 9,
          "column": 6,
          "specs": [
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 460,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 942,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:14.273Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-ab0ba353e84dbf92564b",
              "file": "installation.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 460,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 192,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('service-worker.js is present and versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('service-worker.js is present and versioned', async ({ page }) => {\n    at /var/www/360lm/tests/installation.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:15.231Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/installation-Installation-—-Smoke-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation-—-Smoke-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 461,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 297,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('service-worker.js is present and versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('service-worker.js is present and versioned', async ({ page }) => {\n    at /var/www/360lm/tests/installation.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:16.276Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/installation-Installation-—-Smoke-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation-—-Smoke-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-ccc9a7213e45709226b4",
              "file": "installation.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "service-worker.js is present and versioned",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 462,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 281,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'service-worker.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('btl-install-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'service-worker.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('btl-install-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/installation.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:17.568Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/installation-Installation--52033-js-is-present-and-versioned-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--52033-js-is-present-and-versioned-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 463,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 287,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'service-worker.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('btl-install-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'service-worker.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('btl-install-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/installation.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:18.767Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/installation-Installation--52033-js-is-present-and-versioned-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--52033-js-is-present-and-versioned-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-df9036e1523922d60c03",
              "file": "installation.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1467,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 938,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:07.952Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-d4f772961a365f451b19",
              "file": "installation.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1467,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 169,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('service-worker.js is present and versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('service-worker.js is present and versioned', async ({ page }) => {\n    at /var/www/360lm/tests/installation.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:08.904Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/installation-Installation-—-Smoke-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation-—-Smoke-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 1468,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 257,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('service-worker.js is present and versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('service-worker.js is present and versioned', async ({ page }) => {\n    at /var/www/360lm/tests/installation.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:09.888Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/installation-Installation-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-54081abda27ab20ab7f1",
              "file": "installation.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "service-worker.js is present and versioned",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1469,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'service-worker.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('btl-install-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'service-worker.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('btl-install-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/installation.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:11.109Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/installation-Installation--52033-js-is-present-and-versioned-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--52033-js-is-present-and-versioned-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 1470,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 274,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'service-worker.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('btl-install-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"btl-install-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'service-worker.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('btl-install-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/installation.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:12.314Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/installation-Installation--52033-js-is-present-and-versioned-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--52033-js-is-present-and-versioned-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-637adec38220770de49f",
              "file": "installation.spec.js",
              "line": 22,
              "column": 3
            }
          ]
        },
        {
          "title": "Installation — HTML Structure",
          "file": "installation.spec.js",
          "line": 29,
          "column": 6,
          "specs": [
            {
              "title": "supervisor header and field header present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 464,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 20,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('supervisor header and field header present', () => {\n> 40 |     expect(html).toContain('id=\"supHeader\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"fieldHeader\"');\n  42 |   });\n  43 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('supervisor header and field header present', () => {\n> 40 |     expect(html).toContain('id=\"supHeader\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"fieldHeader\"');\n  42 |   });\n  43 |\n    at /var/www/360lm/tests/installation.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:20.042Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--1b8e3-er-and-field-header-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    },
                    {
                      "workerIndex": 465,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 23,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('supervisor header and field header present', () => {\n> 40 |     expect(html).toContain('id=\"supHeader\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"fieldHeader\"');\n  42 |   });\n  43 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('supervisor header and field header present', () => {\n> 40 |     expect(html).toContain('id=\"supHeader\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"fieldHeader\"');\n  42 |   });\n  43 |\n    at /var/www/360lm/tests/installation.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:21.025Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--1b8e3-er-and-field-header-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-5ff2db5a852b4e369ccf",
              "file": "installation.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "supervisor header has Hub home button",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 466,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('supervisor header has Hub home button', () => {\n> 45 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('supervisor header has Hub home button', () => {\n> 45 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/installation.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:22.003Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--58736--header-has-Hub-home-button-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    },
                    {
                      "workerIndex": 467,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('supervisor header has Hub home button', () => {\n> 45 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('supervisor header has Hub home button', () => {\n> 45 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/installation.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:22.904Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--58736--header-has-Hub-home-button-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-5c5cefe4b7d9352a02c8",
              "file": "installation.spec.js",
              "line": 44,
              "column": 3
            },
            {
              "title": "APP.supervisorLogout function present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 468,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('APP.supervisorLogout function present', () => {\n> 50 |     expect(html).toContain('supervisorLogout');\n     |                  ^\n  51 |   });\n  52 | });\n  53 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('APP.supervisorLogout function present', () => {\n> 50 |     expect(html).toContain('supervisorLogout');\n     |                  ^\n  51 |   });\n  52 | });\n  53 |\n    at /var/www/360lm/tests/installation.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:23.797Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--bb1aa-isorLogout-function-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    },
                    {
                      "workerIndex": 469,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('APP.supervisorLogout function present', () => {\n> 50 |     expect(html).toContain('supervisorLogout');\n     |                  ^\n  51 |   });\n  52 | });\n  53 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('APP.supervisorLogout function present', () => {\n> 50 |     expect(html).toContain('supervisorLogout');\n     |                  ^\n  51 |   });\n  52 | });\n  53 |\n    at /var/www/360lm/tests/installation.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:24.662Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--bb1aa-isorLogout-function-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-82309655270ca6f8f73a",
              "file": "installation.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "supervisor header and field header present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1471,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 17,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('supervisor header and field header present', () => {\n> 40 |     expect(html).toContain('id=\"supHeader\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"fieldHeader\"');\n  42 |   });\n  43 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('supervisor header and field header present', () => {\n> 40 |     expect(html).toContain('id=\"supHeader\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"fieldHeader\"');\n  42 |   });\n  43 |\n    at /var/www/360lm/tests/installation.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:13.520Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--1b8e3-er-and-field-header-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    },
                    {
                      "workerIndex": 1472,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('supervisor header and field header present', () => {\n> 40 |     expect(html).toContain('id=\"supHeader\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"fieldHeader\"');\n  42 |   });\n  43 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"supHeader\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('supervisor header and field header present', () => {\n> 40 |     expect(html).toContain('id=\"supHeader\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"fieldHeader\"');\n  42 |   });\n  43 |\n    at /var/www/360lm/tests/installation.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:14.435Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--1b8e3-er-and-field-header-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-c734a551cb9837d291a9",
              "file": "installation.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "supervisor header has Hub home button",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1473,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('supervisor header has Hub home button', () => {\n> 45 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('supervisor header has Hub home button', () => {\n> 45 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/installation.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:15.276Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--58736--header-has-Hub-home-button-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    },
                    {
                      "workerIndex": 1474,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('supervisor header has Hub home button', () => {\n> 45 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('supervisor header has Hub home button', () => {\n> 45 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/installation.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:16.121Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--58736--header-has-Hub-home-button-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-1c9f721eaf2fa5da68c9",
              "file": "installation.spec.js",
              "line": 44,
              "column": 3
            },
            {
              "title": "APP.supervisorLogout function present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1475,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('APP.supervisorLogout function present', () => {\n> 50 |     expect(html).toContain('supervisorLogout');\n     |                  ^\n  51 |   });\n  52 | });\n  53 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('APP.supervisorLogout function present', () => {\n> 50 |     expect(html).toContain('supervisorLogout');\n     |                  ^\n  51 |   });\n  52 | });\n  53 |\n    at /var/www/360lm/tests/installation.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:17.124Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--bb1aa-isorLogout-function-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    },
                    {
                      "workerIndex": 1476,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 12,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/installation.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/installation.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('APP.supervisorLogout function present', () => {\n> 50 |     expect(html).toContain('supervisorLogout');\n     |                  ^\n  51 |   });\n  52 | });\n  53 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/installation.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"supervisorLogout\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('APP.supervisorLogout function present', () => {\n> 50 |     expect(html).toContain('supervisorLogout');\n     |                  ^\n  51 |   });\n  52 | });\n  53 |\n    at /var/www/360lm/tests/installation.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:17.993Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/installation-Installation--bb1aa-isorLogout-function-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/installation.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "611c3ab09f890f6f5b40-e28b6097f2bc4cc44fa1",
              "file": "installation.spec.js",
              "line": 49,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "learn.spec.js",
      "file": "learn.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Learning Hub — Smoke",
          "file": "learn.spec.js",
          "line": 9,
          "column": 6,
          "specs": [
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 470,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1010,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:25.530Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-da9d407377b8127594cf",
              "file": "learn.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 470,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 204,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/learn.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:26.691Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 471,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 292,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/learn.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:27.753Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-e982d1c0d9914b0c9dd7",
              "file": "learn.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 472,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 280,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('learn-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('learn-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/learn.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:29.048Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-sw-js-is-versioned-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-sw-js-is-versioned-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 473,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 307,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('learn-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('learn-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/learn.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:30.413Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-sw-js-is-versioned-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-sw-js-is-versioned-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-2fac8aeafdd20b65430b",
              "file": "learn.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1477,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1055,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:18.883Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-65211b7834114efb6205",
              "file": "learn.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1477,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 196,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/learn.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:20.113Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 1478,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 276,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/learn.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:21.127Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-01af94099a084dc16502",
              "file": "learn.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1479,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 297,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('learn-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('learn-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/learn.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:22.397Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-sw-js-is-versioned-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-sw-js-is-versioned-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 1480,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 277,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('learn-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"learn-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('learn-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/learn.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:23.611Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-sw-js-is-versioned-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-Smoke-sw-js-is-versioned-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-4ef458290a1be671fcaf",
              "file": "learn.spec.js",
              "line": 22,
              "column": 3
            }
          ]
        },
        {
          "title": "Learning Hub — HTML Structure",
          "file": "learn.spec.js",
          "line": 29,
          "column": 6,
          "specs": [
            {
              "title": "app container and catalog view present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 474,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('app container and catalog view present', () => {\n> 40 |     expect(html).toContain('id=\"boot\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |     expect(html).toContain('id=\"view-catalog\"');\n  43 |     expect(html).toContain('id=\"groups-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('app container and catalog view present', () => {\n> 40 |     expect(html).toContain('id=\"boot\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |     expect(html).toContain('id=\"view-catalog\"');\n  43 |     expect(html).toContain('id=\"groups-container\"');\n    at /var/www/360lm/tests/learn.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:31.754Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--61561-er-and-catalog-view-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    },
                    {
                      "workerIndex": 475,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('app container and catalog view present', () => {\n> 40 |     expect(html).toContain('id=\"boot\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |     expect(html).toContain('id=\"view-catalog\"');\n  43 |     expect(html).toContain('id=\"groups-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('app container and catalog view present', () => {\n> 40 |     expect(html).toContain('id=\"boot\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |     expect(html).toContain('id=\"view-catalog\"');\n  43 |     expect(html).toContain('id=\"groups-container\"');\n    at /var/www/360lm/tests/learn.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:32.663Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--61561-er-and-catalog-view-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-100d855a6cc5137529db",
              "file": "learn.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub navigation button present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 476,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:47:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 47
                        },
                        "snippet": "  45 |\n  46 |   test('hub navigation button present', () => {\n> 47 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  48 |     expect(html).toContain('🏡 Hub');\n  49 |   });\n  50 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 47
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  45 |\n  46 |   test('hub navigation button present', () => {\n> 47 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  48 |     expect(html).toContain('🏡 Hub');\n  49 |   });\n  50 |\n    at /var/www/360lm/tests/learn.spec.js:47:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:33.581Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--8a000-b-navigation-button-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 47
                      }
                    },
                    {
                      "workerIndex": 477,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:47:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 47
                        },
                        "snippet": "  45 |\n  46 |   test('hub navigation button present', () => {\n> 47 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  48 |     expect(html).toContain('🏡 Hub');\n  49 |   });\n  50 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 47
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  45 |\n  46 |   test('hub navigation button present', () => {\n> 47 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  48 |     expect(html).toContain('🏡 Hub');\n  49 |   });\n  50 |\n    at /var/www/360lm/tests/learn.spec.js:47:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:34.460Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--8a000-b-navigation-button-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 47
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-a735397329fda72dd584",
              "file": "learn.spec.js",
              "line": 46,
              "column": 3
            },
            {
              "title": "team view and courses-soon block present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 478,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:52:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 52
                        },
                        "snippet": "  50 |\n  51 |   test('team view and courses-soon block present', () => {\n> 52 |     expect(html).toContain('id=\"view-team\"');\n     |                  ^\n  53 |     expect(html).toContain('id=\"courses-soon-block\"');\n  54 |     expect(html).toContain('Coming Soon');\n  55 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 52
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  50 |\n  51 |   test('team view and courses-soon block present', () => {\n> 52 |     expect(html).toContain('id=\"view-team\"');\n     |                  ^\n  53 |     expect(html).toContain('id=\"courses-soon-block\"');\n  54 |     expect(html).toContain('Coming Soon');\n  55 |   });\n    at /var/www/360lm/tests/learn.spec.js:52:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:35.345Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--3a025--courses-soon-block-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 52
                      }
                    },
                    {
                      "workerIndex": 479,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:52:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 52
                        },
                        "snippet": "  50 |\n  51 |   test('team view and courses-soon block present', () => {\n> 52 |     expect(html).toContain('id=\"view-team\"');\n     |                  ^\n  53 |     expect(html).toContain('id=\"courses-soon-block\"');\n  54 |     expect(html).toContain('Coming Soon');\n  55 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 52
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  50 |\n  51 |   test('team view and courses-soon block present', () => {\n> 52 |     expect(html).toContain('id=\"view-team\"');\n     |                  ^\n  53 |     expect(html).toContain('id=\"courses-soon-block\"');\n  54 |     expect(html).toContain('Coming Soon');\n  55 |   });\n    at /var/www/360lm/tests/learn.spec.js:52:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:36.284Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--3a025--courses-soon-block-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 52
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-3d6299e5e11a4f3a877e",
              "file": "learn.spec.js",
              "line": 51,
              "column": 3
            },
            {
              "title": "app container and catalog view present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1481,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('app container and catalog view present', () => {\n> 40 |     expect(html).toContain('id=\"boot\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |     expect(html).toContain('id=\"view-catalog\"');\n  43 |     expect(html).toContain('id=\"groups-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('app container and catalog view present', () => {\n> 40 |     expect(html).toContain('id=\"boot\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |     expect(html).toContain('id=\"view-catalog\"');\n  43 |     expect(html).toContain('id=\"groups-container\"');\n    at /var/www/360lm/tests/learn.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:24.803Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--61561-er-and-catalog-view-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    },
                    {
                      "workerIndex": 1482,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 16,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('app container and catalog view present', () => {\n> 40 |     expect(html).toContain('id=\"boot\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |     expect(html).toContain('id=\"view-catalog\"');\n  43 |     expect(html).toContain('id=\"groups-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"boot\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('app container and catalog view present', () => {\n> 40 |     expect(html).toContain('id=\"boot\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"app\"');\n  42 |     expect(html).toContain('id=\"view-catalog\"');\n  43 |     expect(html).toContain('id=\"groups-container\"');\n    at /var/www/360lm/tests/learn.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:25.652Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--61561-er-and-catalog-view-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-e3ea57f761a103bcc0c6",
              "file": "learn.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub navigation button present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1483,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:47:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 47
                        },
                        "snippet": "  45 |\n  46 |   test('hub navigation button present', () => {\n> 47 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  48 |     expect(html).toContain('🏡 Hub');\n  49 |   });\n  50 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 47
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  45 |\n  46 |   test('hub navigation button present', () => {\n> 47 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  48 |     expect(html).toContain('🏡 Hub');\n  49 |   });\n  50 |\n    at /var/www/360lm/tests/learn.spec.js:47:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:26.572Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--8a000-b-navigation-button-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 47
                      }
                    },
                    {
                      "workerIndex": 1484,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 31,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:47:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 47
                        },
                        "snippet": "  45 |\n  46 |   test('hub navigation button present', () => {\n> 47 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  48 |     expect(html).toContain('🏡 Hub');\n  49 |   });\n  50 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 47
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  45 |\n  46 |   test('hub navigation button present', () => {\n> 47 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  48 |     expect(html).toContain('🏡 Hub');\n  49 |   });\n  50 |\n    at /var/www/360lm/tests/learn.spec.js:47:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:27.526Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--8a000-b-navigation-button-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 47
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-e459acc18bbb17689cdc",
              "file": "learn.spec.js",
              "line": 46,
              "column": 3
            },
            {
              "title": "team view and courses-soon block present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1485,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:52:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 52
                        },
                        "snippet": "  50 |\n  51 |   test('team view and courses-soon block present', () => {\n> 52 |     expect(html).toContain('id=\"view-team\"');\n     |                  ^\n  53 |     expect(html).toContain('id=\"courses-soon-block\"');\n  54 |     expect(html).toContain('Coming Soon');\n  55 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 52
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  50 |\n  51 |   test('team view and courses-soon block present', () => {\n> 52 |     expect(html).toContain('id=\"view-team\"');\n     |                  ^\n  53 |     expect(html).toContain('id=\"courses-soon-block\"');\n  54 |     expect(html).toContain('Coming Soon');\n  55 |   });\n    at /var/www/360lm/tests/learn.spec.js:52:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:28.532Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--3a025--courses-soon-block-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 52
                      }
                    },
                    {
                      "workerIndex": 1486,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/learn.spec.js:52:18",
                        "location": {
                          "file": "/var/www/360lm/tests/learn.spec.js",
                          "column": 18,
                          "line": 52
                        },
                        "snippet": "  50 |\n  51 |   test('team view and courses-soon block present', () => {\n> 52 |     expect(html).toContain('id=\"view-team\"');\n     |                  ^\n  53 |     expect(html).toContain('id=\"courses-soon-block\"');\n  54 |     expect(html).toContain('Coming Soon');\n  55 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/learn.spec.js",
                            "column": 18,
                            "line": 52
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"view-team\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  50 |\n  51 |   test('team view and courses-soon block present', () => {\n> 52 |     expect(html).toContain('id=\"view-team\"');\n     |                  ^\n  53 |     expect(html).toContain('id=\"courses-soon-block\"');\n  54 |     expect(html).toContain('Coming Soon');\n  55 |   });\n    at /var/www/360lm/tests/learn.spec.js:52:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:29.466Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/learn-Learning-Hub-—-HTML--3a025--courses-soon-block-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/learn.spec.js",
                        "column": 18,
                        "line": 52
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4ddeaf02d2d282b91098-c868969ab34afb5c8627",
              "file": "learn.spec.js",
              "line": 51,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "mappls-creds.spec.js",
      "file": "mappls-creds.spec.js",
      "column": 0,
      "line": 0,
      "specs": [
        {
          "title": "extract Mappls Client ID and Client Secret",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [
                {
                  "type": "skip",
                  "description": "Set MAPPLS_EMAIL and MAPPLS_PASS env vars.\nRun: MAPPLS_EMAIL=\"x@y.com\" MAPPLS_PASS=\"secret\" npx playwright test tests/mappls-creds.spec.js --project=desktop-chrome",
                  "location": {
                    "file": "/var/www/360lm/tests/mappls-creds.spec.js",
                    "line": 22,
                    "column": 8
                  }
                }
              ],
              "expectedStatus": "skipped",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 988,
                  "parallelIndex": 0,
                  "status": "skipped",
                  "duration": 192,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T21:37:49.942Z",
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Set MAPPLS_EMAIL and MAPPLS_PASS env vars.\nRun: MAPPLS_EMAIL=\"x@y.com\" MAPPLS_PASS=\"secret\" npx playwright test tests/mappls-creds.spec.js --project=desktop-chrome",
                      "location": {
                        "file": "/var/www/360lm/tests/mappls-creds.spec.js",
                        "line": 22,
                        "column": 8
                      }
                    }
                  ],
                  "attachments": []
                }
              ],
              "status": "skipped"
            }
          ],
          "id": "2620e5780324944818ba-0f91a0f75835e9a22194",
          "file": "mappls-creds.spec.js",
          "line": 18,
          "column": 1
        },
        {
          "title": "extract Mappls Client ID and Client Secret",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [
                {
                  "type": "skip",
                  "description": "Set MAPPLS_EMAIL and MAPPLS_PASS env vars.\nRun: MAPPLS_EMAIL=\"x@y.com\" MAPPLS_PASS=\"secret\" npx playwright test tests/mappls-creds.spec.js --project=desktop-chrome",
                  "location": {
                    "file": "/var/www/360lm/tests/mappls-creds.spec.js",
                    "line": 22,
                    "column": 8
                  }
                }
              ],
              "expectedStatus": "skipped",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1996,
                  "parallelIndex": 0,
                  "status": "skipped",
                  "duration": 198,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-16T00:38:29.217Z",
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "Set MAPPLS_EMAIL and MAPPLS_PASS env vars.\nRun: MAPPLS_EMAIL=\"x@y.com\" MAPPLS_PASS=\"secret\" npx playwright test tests/mappls-creds.spec.js --project=desktop-chrome",
                      "location": {
                        "file": "/var/www/360lm/tests/mappls-creds.spec.js",
                        "line": 22,
                        "column": 8
                      }
                    }
                  ],
                  "attachments": []
                }
              ],
              "status": "skipped"
            }
          ],
          "id": "2620e5780324944818ba-f12c4e35ffb64e6c852b",
          "file": "mappls-creds.spec.js",
          "line": 18,
          "column": 1
        }
      ]
    },
    {
      "title": "oc_admin.spec.js",
      "file": "oc_admin.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "OC Admin — Smoke",
          "file": "oc_admin.spec.js",
          "line": 9,
          "column": 6,
          "specs": [
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 480,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1054,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:37.213Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "267fa811539e39ab3575-8a14b65150477ee9cf1c",
              "file": "oc_admin.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 480,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 204,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |\n    at /var/www/360lm/tests/oc_admin.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:38.432Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-Smoke-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-Smoke-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 481,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 307,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |\n    at /var/www/360lm/tests/oc_admin.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:39.476Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-Smoke-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-Smoke-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "267fa811539e39ab3575-eee6ba9c91fea9959360",
              "file": "oc_admin.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1487,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1073,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:30.427Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "267fa811539e39ab3575-72bd2805d2a3f2e8ddf0",
              "file": "oc_admin.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1487,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 218,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |\n    at /var/www/360lm/tests/oc_admin.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:31.664Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-Smoke-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-Smoke-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 1488,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 277,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |\n    at /var/www/360lm/tests/oc_admin.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:32.772Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "267fa811539e39ab3575-1beb9d9318c1d7cc2339",
              "file": "oc_admin.spec.js",
              "line": 17,
              "column": 3
            }
          ]
        },
        {
          "title": "OC Admin — HTML Structure",
          "file": "oc_admin.spec.js",
          "line": 25,
          "column": 6,
          "specs": [
            {
              "title": "all screens present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 482,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 20,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:36:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 36
                        },
                        "snippet": "  34 |\n  35 |   test('all screens present', () => {\n> 36 |     expect(html).toContain('id=\"scr-home\"');\n     |                  ^\n  37 |     expect(html).toContain('id=\"scr-import\"');\n  38 |     expect(html).toContain('id=\"scr-review\"');\n  39 |     expect(html).toContain('id=\"scr-results\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  34 |\n  35 |   test('all screens present', () => {\n> 36 |     expect(html).toContain('id=\"scr-home\"');\n     |                  ^\n  37 |     expect(html).toContain('id=\"scr-import\"');\n  38 |     expect(html).toContain('id=\"scr-review\"');\n  39 |     expect(html).toContain('id=\"scr-results\"');\n    at /var/www/360lm/tests/oc_admin.spec.js:36:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:40.855Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-Structure-all-screens-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 36
                      }
                    },
                    {
                      "workerIndex": 483,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:36:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 36
                        },
                        "snippet": "  34 |\n  35 |   test('all screens present', () => {\n> 36 |     expect(html).toContain('id=\"scr-home\"');\n     |                  ^\n  37 |     expect(html).toContain('id=\"scr-import\"');\n  38 |     expect(html).toContain('id=\"scr-review\"');\n  39 |     expect(html).toContain('id=\"scr-results\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  34 |\n  35 |   test('all screens present', () => {\n> 36 |     expect(html).toContain('id=\"scr-home\"');\n     |                  ^\n  37 |     expect(html).toContain('id=\"scr-import\"');\n  38 |     expect(html).toContain('id=\"scr-review\"');\n  39 |     expect(html).toContain('id=\"scr-results\"');\n    at /var/www/360lm/tests/oc_admin.spec.js:36:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:41.715Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-Structure-all-screens-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 36
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "267fa811539e39ab3575-0a2f91e8d75ff0c8b7da",
              "file": "oc_admin.spec.js",
              "line": 35,
              "column": 3
            },
            {
              "title": "hub navigation on every screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 484,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('hub navigation on every screen', () => {\n> 45 |     expect(html).toContain('goHub()');\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('hub navigation on every screen', () => {\n> 45 |     expect(html).toContain('goHub()');\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/oc_admin.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:42.549Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-S-b7f7a--navigation-on-every-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    },
                    {
                      "workerIndex": 485,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('hub navigation on every screen', () => {\n> 45 |     expect(html).toContain('goHub()');\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('hub navigation on every screen', () => {\n> 45 |     expect(html).toContain('goHub()');\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/oc_admin.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:43.431Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-S-b7f7a--navigation-on-every-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "267fa811539e39ab3575-c8d552c595132fc94885",
              "file": "oc_admin.spec.js",
              "line": 44,
              "column": 3
            },
            {
              "title": "contact import functions present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 486,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('contact import functions present', () => {\n> 50 |     expect(html).toContain('function goHome(');\n     |                  ^\n  51 |     expect(html).toContain('function goConflicts(');\n  52 |   });\n  53 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('contact import functions present', () => {\n> 50 |     expect(html).toContain('function goHome(');\n     |                  ^\n  51 |     expect(html).toContain('function goConflicts(');\n  52 |   });\n  53 | });\n    at /var/www/360lm/tests/oc_admin.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:44.350Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-S-62629-ct-import-functions-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    },
                    {
                      "workerIndex": 487,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 12,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('contact import functions present', () => {\n> 50 |     expect(html).toContain('function goHome(');\n     |                  ^\n  51 |     expect(html).toContain('function goConflicts(');\n  52 |   });\n  53 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('contact import functions present', () => {\n> 50 |     expect(html).toContain('function goHome(');\n     |                  ^\n  51 |     expect(html).toContain('function goConflicts(');\n  52 |   });\n  53 | });\n    at /var/www/360lm/tests/oc_admin.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:26:45.200Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-S-62629-ct-import-functions-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "267fa811539e39ab3575-de4c10cea596e3112408",
              "file": "oc_admin.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "all screens present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1489,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 17,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:36:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 36
                        },
                        "snippet": "  34 |\n  35 |   test('all screens present', () => {\n> 36 |     expect(html).toContain('id=\"scr-home\"');\n     |                  ^\n  37 |     expect(html).toContain('id=\"scr-import\"');\n  38 |     expect(html).toContain('id=\"scr-review\"');\n  39 |     expect(html).toContain('id=\"scr-results\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  34 |\n  35 |   test('all screens present', () => {\n> 36 |     expect(html).toContain('id=\"scr-home\"');\n     |                  ^\n  37 |     expect(html).toContain('id=\"scr-import\"');\n  38 |     expect(html).toContain('id=\"scr-review\"');\n  39 |     expect(html).toContain('id=\"scr-results\"');\n    at /var/www/360lm/tests/oc_admin.spec.js:36:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:34.007Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-Structure-all-screens-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 36
                      }
                    },
                    {
                      "workerIndex": 1490,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:36:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 36
                        },
                        "snippet": "  34 |\n  35 |   test('all screens present', () => {\n> 36 |     expect(html).toContain('id=\"scr-home\"');\n     |                  ^\n  37 |     expect(html).toContain('id=\"scr-import\"');\n  38 |     expect(html).toContain('id=\"scr-review\"');\n  39 |     expect(html).toContain('id=\"scr-results\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"scr-home\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  34 |\n  35 |   test('all screens present', () => {\n> 36 |     expect(html).toContain('id=\"scr-home\"');\n     |                  ^\n  37 |     expect(html).toContain('id=\"scr-import\"');\n  38 |     expect(html).toContain('id=\"scr-review\"');\n  39 |     expect(html).toContain('id=\"scr-results\"');\n    at /var/www/360lm/tests/oc_admin.spec.js:36:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:34.877Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-Structure-all-screens-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 36
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "267fa811539e39ab3575-759189fba0e10e60edc9",
              "file": "oc_admin.spec.js",
              "line": 35,
              "column": 3
            },
            {
              "title": "hub navigation on every screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1491,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('hub navigation on every screen', () => {\n> 45 |     expect(html).toContain('goHub()');\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('hub navigation on every screen', () => {\n> 45 |     expect(html).toContain('goHub()');\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/oc_admin.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:35.767Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-S-b7f7a--navigation-on-every-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    },
                    {
                      "workerIndex": 1492,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:45:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 45
                        },
                        "snippet": "  43 |\n  44 |   test('hub navigation on every screen', () => {\n> 45 |     expect(html).toContain('goHub()');\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"goHub()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  43 |\n  44 |   test('hub navigation on every screen', () => {\n> 45 |     expect(html).toContain('goHub()');\n     |                  ^\n  46 |     expect(html).toContain('🏡 Hub');\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/oc_admin.spec.js:45:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:36.637Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-S-b7f7a--navigation-on-every-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 45
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "267fa811539e39ab3575-afa56a5d9bada41c124e",
              "file": "oc_admin.spec.js",
              "line": 44,
              "column": 3
            },
            {
              "title": "contact import functions present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1493,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('contact import functions present', () => {\n> 50 |     expect(html).toContain('function goHome(');\n     |                  ^\n  51 |     expect(html).toContain('function goConflicts(');\n  52 |   });\n  53 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('contact import functions present', () => {\n> 50 |     expect(html).toContain('function goHome(');\n     |                  ^\n  51 |     expect(html).toContain('function goConflicts(');\n  52 |   });\n  53 | });\n    at /var/www/360lm/tests/oc_admin.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:37.519Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-S-62629-ct-import-functions-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    },
                    {
                      "workerIndex": 1494,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/oc_admin.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/oc_admin.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('contact import functions present', () => {\n> 50 |     expect(html).toContain('function goHome(');\n     |                  ^\n  51 |     expect(html).toContain('function goConflicts(');\n  52 |   });\n  53 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/oc_admin.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function goHome(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('contact import functions present', () => {\n> 50 |     expect(html).toContain('function goHome(');\n     |                  ^\n  51 |     expect(html).toContain('function goConflicts(');\n  52 |   });\n  53 | });\n    at /var/www/360lm/tests/oc_admin.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:26:38.437Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/oc_admin-OC-Admin-—-HTML-S-62629-ct-import-functions-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/oc_admin.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "267fa811539e39ab3575-e0bcf03d76234ecbebcd",
              "file": "oc_admin.spec.js",
              "line": 49,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "printing.spec.js",
      "file": "printing.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Print Nest — Page Load",
          "file": "printing.spec.js",
          "line": 20,
          "column": 6,
          "specs": [
            {
              "title": "no session → redirects to hub",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 562,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:46.067Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-e9a03ed15400c0adb24e",
              "file": "printing.spec.js",
              "line": 21,
              "column": 3
            },
            {
              "title": "with session → dashboard visible",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1080,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:46.796Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-7006444a9ba7177df2b0",
              "file": "printing.spec.js",
              "line": 26,
              "column": 3
            },
            {
              "title": "manifest.json reachable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 179,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:47.910Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-c5d807eb50919c436141",
              "file": "printing.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "service worker registers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1062,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:48.119Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-1b08180ed543df04f851",
              "file": "printing.spec.js",
              "line": 40,
              "column": 3
            },
            {
              "title": "no session → redirects to hub",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 571,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:39.443Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-599665ed77b1c5a0d64d",
              "file": "printing.spec.js",
              "line": 21,
              "column": 3
            },
            {
              "title": "with session → dashboard visible",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1044,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:40.184Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-517b817b11b2ee7ec7ee",
              "file": "printing.spec.js",
              "line": 26,
              "column": 3
            },
            {
              "title": "manifest.json reachable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 170,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:41.255Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-31d2c9edd77ee2f30204",
              "file": "printing.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "service worker registers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1076,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:41.444Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-b63a0ee0f09ecbe80cab",
              "file": "printing.spec.js",
              "line": 40,
              "column": 3
            }
          ]
        },
        {
          "title": "Print Nest — Dashboard",
          "file": "printing.spec.js",
          "line": 52,
          "column": 6,
          "specs": [
            {
              "title": "stat boxes visible",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1074,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:49.200Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-fa405ea4ee770d0637ea",
              "file": "printing.spec.js",
              "line": 55,
              "column": 3
            },
            {
              "title": "user name shown in header",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1062,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:50.294Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-e9f3cdfe74b455808006",
              "file": "printing.spec.js",
              "line": 63,
              "column": 3
            },
            {
              "title": "Start New Nesting button navigates to Add screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1089,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:51.390Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-47f5bb5b71ebdce00a51",
              "file": "printing.spec.js",
              "line": 69,
              "column": 3
            },
            {
              "title": "bottom nav Runs tab loads runs screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1088,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:52.501Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-7a666b0a97a452261553",
              "file": "printing.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "bottom nav Stock tab loads stock screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1127,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:53.608Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-cc32a17fcdcd303b5530",
              "file": "printing.spec.js",
              "line": 83,
              "column": 3
            },
            {
              "title": "stat boxes visible",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1095,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:42.540Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-e1dba80098461a38f898",
              "file": "printing.spec.js",
              "line": 55,
              "column": 3
            },
            {
              "title": "user name shown in header",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1062,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:43.651Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-9ca22474619662f7c0bc",
              "file": "printing.spec.js",
              "line": 63,
              "column": 3
            },
            {
              "title": "Start New Nesting button navigates to Add screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1099,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:44.742Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-44fb3cd490b0d388c929",
              "file": "printing.spec.js",
              "line": 69,
              "column": 3
            },
            {
              "title": "bottom nav Runs tab loads runs screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1107,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:45.858Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-d4ac55b6502fc643652d",
              "file": "printing.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "bottom nav Stock tab loads stock screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1182,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:46.982Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-1a95200c08fd862f955b",
              "file": "printing.spec.js",
              "line": 83,
              "column": 3
            }
          ]
        },
        {
          "title": "Print Nest — Add Items (Manual)",
          "file": "printing.spec.js",
          "line": 91,
          "column": 6,
          "specs": [
            {
              "title": "Compute button disabled with no items",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1177,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:54.752Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-e179d9dfe158529b0fb2",
              "file": "printing.spec.js",
              "line": 102,
              "column": 3
            },
            {
              "title": "empty form shows validation toast",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1316,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:55.950Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-d4910943ff27bc3b36a2",
              "file": "printing.spec.js",
              "line": 106,
              "column": 3
            },
            {
              "title": "valid item adds to list and enables Compute",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1308,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:57.284Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-54e05ee6fecacfc0cee7",
              "file": "printing.spec.js",
              "line": 111,
              "column": 3
            },
            {
              "title": "removing item disables Compute again",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1434,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:26:58.614Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-399d972b0fcd44fa6fe8",
              "file": "printing.spec.js",
              "line": 128,
              "column": 3
            },
            {
              "title": "multiple items accumulate in list",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1771,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:00.069Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-622332b7a31b39260603",
              "file": "printing.spec.js",
              "line": 139,
              "column": 3
            },
            {
              "title": "bleed margins section expands",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1607,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:01.882Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-b89251c9b0cdd647b45e",
              "file": "printing.spec.js",
              "line": 151,
              "column": 3
            },
            {
              "title": "Compute button disabled with no items",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1173,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:48.189Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-c2394bb0a2591953ace2",
              "file": "printing.spec.js",
              "line": 102,
              "column": 3
            },
            {
              "title": "empty form shows validation toast",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1231,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:49.382Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-a2c2aed7551e2edd1e70",
              "file": "printing.spec.js",
              "line": 106,
              "column": 3
            },
            {
              "title": "valid item adds to list and enables Compute",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1346,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:50.629Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-c4a183df5d2e20dec15e",
              "file": "printing.spec.js",
              "line": 111,
              "column": 3
            },
            {
              "title": "removing item disables Compute again",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1359,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:51.994Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-4550ef4778ee5e898c37",
              "file": "printing.spec.js",
              "line": 128,
              "column": 3
            },
            {
              "title": "multiple items accumulate in list",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1508,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:53.379Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-72cae6b844b178f8b174",
              "file": "printing.spec.js",
              "line": 139,
              "column": 3
            },
            {
              "title": "bleed margins section expands",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1217,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:54.902Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-2e472695fc5bf56ec57f",
              "file": "printing.spec.js",
              "line": 151,
              "column": 3
            }
          ]
        },
        {
          "title": "Print Nest — Nesting Engine",
          "file": "printing.spec.js",
          "line": 158,
          "column": 6,
          "specs": [
            {
              "title": "3×6 Flex → 1-roll 3ft, 18 sqft/pc (narrow=3 → 3ft roll)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1611,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:03.518Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-5cafecf53aee7e1b2b16",
              "file": "printing.spec.js",
              "line": 178,
              "column": 3
            },
            {
              "title": "7ft wide Flex → 2-roll (3+4ft) is available",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1565,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:05.169Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-70ff6019d0a5c87828da",
              "file": "printing.spec.js",
              "line": 193,
              "column": 3
            },
            {
              "title": "item >5ft shows outsource/join buttons",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1417,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:06.751Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-7e40f609da5de5265042",
              "file": "printing.spec.js",
              "line": 207,
              "column": 3
            },
            {
              "title": "gang plan groups same roll-width items together",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1501,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:08.185Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-6e27e03e92e572b90617",
              "file": "printing.spec.js",
              "line": 220,
              "column": 3
            },
            {
              "title": "Flex and Vinyl items split into separate groups",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1472,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:09.703Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-73997ee3ee4043d443ea",
              "file": "printing.spec.js",
              "line": 234,
              "column": 3
            },
            {
              "title": "Save button opens save modal",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1359,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:11.194Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-1c6758088a97dceed358",
              "file": "printing.spec.js",
              "line": 242,
              "column": 3
            },
            {
              "title": "AI review button visible after computing nest",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1314,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:12.572Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-9e107b68e8911f92ed23",
              "file": "printing.spec.js",
              "line": 250,
              "column": 3
            },
            {
              "title": "AI review button opens chat modal",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1431,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:13.909Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-189c1833ae2688412e71",
              "file": "printing.spec.js",
              "line": 258,
              "column": 3
            },
            {
              "title": "Copy plan button visible on nest screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1302,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:15.358Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-393eaf8c973ebe982a39",
              "file": "printing.spec.js",
              "line": 268,
              "column": 3
            },
            {
              "title": "Copy plan writes text to clipboard and shows toast",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1407,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:16.679Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-09429b972abab1567049",
              "file": "printing.spec.js",
              "line": 275,
              "column": 3
            },
            {
              "title": "3×6 Flex → 1-roll 3ft, 18 sqft/pc (narrow=3 → 3ft roll)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1409,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:56.134Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-47c53cb59920ccacabce",
              "file": "printing.spec.js",
              "line": 178,
              "column": 3
            },
            {
              "title": "7ft wide Flex → 2-roll (3+4ft) is available",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1556,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:57.560Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-5db5e7122f08c65c6e5d",
              "file": "printing.spec.js",
              "line": 193,
              "column": 3
            },
            {
              "title": "item >5ft shows outsource/join buttons",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1648,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:26:59.134Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-8c7c13efddd6faf596bd",
              "file": "printing.spec.js",
              "line": 207,
              "column": 3
            },
            {
              "title": "gang plan groups same roll-width items together",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2522,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:00.799Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-484a882ae5569bbd49d4",
              "file": "printing.spec.js",
              "line": 220,
              "column": 3
            },
            {
              "title": "Flex and Vinyl items split into separate groups",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1721,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:03.344Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-4419a71daf3798747a1f",
              "file": "printing.spec.js",
              "line": 234,
              "column": 3
            },
            {
              "title": "Save button opens save modal",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1500,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:05.084Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-46cfaee8b74a0d61cc70",
              "file": "printing.spec.js",
              "line": 242,
              "column": 3
            },
            {
              "title": "AI review button visible after computing nest",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1337,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:06.612Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-2b6272c63008b807964a",
              "file": "printing.spec.js",
              "line": 250,
              "column": 3
            },
            {
              "title": "AI review button opens chat modal",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1460,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:07.968Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-0f5371b95d2c82a0779f",
              "file": "printing.spec.js",
              "line": 258,
              "column": 3
            },
            {
              "title": "Copy plan button visible on nest screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1283,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:09.449Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-4d08a127616c207c0cef",
              "file": "printing.spec.js",
              "line": 268,
              "column": 3
            },
            {
              "title": "Copy plan writes text to clipboard and shows toast",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1380,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:10.746Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-0adb747481eb55b6290d",
              "file": "printing.spec.js",
              "line": 275,
              "column": 3
            }
          ]
        },
        {
          "title": "Print Nest — Excel/AI Tab",
          "file": "printing.spec.js",
          "line": 291,
          "column": 6,
          "specs": [
            {
              "title": "Excel tab is active by default on add screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1127,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:18.101Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-472a3a2ce3bb0bf75b89",
              "file": "printing.spec.js",
              "line": 300,
              "column": 3
            },
            {
              "title": "Excel/AI tab shows upload area",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1119,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:19.246Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-a1bf996ecafcd8c382c6",
              "file": "printing.spec.js",
              "line": 307,
              "column": 3
            },
            {
              "title": "Layout button present on nest screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1294,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:20.384Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-f533009b19f0a1a2749a",
              "file": "printing.spec.js",
              "line": 312,
              "column": 3
            },
            {
              "title": "Compute button pulses when items exist",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1263,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:21.700Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-164ab48d656211accbec",
              "file": "printing.spec.js",
              "line": 325,
              "column": 3
            },
            {
              "title": "Excel tab is active by default on add screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1134,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:12.144Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-5d797116170cf6ab836e",
              "file": "printing.spec.js",
              "line": 300,
              "column": 3
            },
            {
              "title": "Excel/AI tab shows upload area",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1117,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:13.301Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-5f8c798e8c9d22dd5e2e",
              "file": "printing.spec.js",
              "line": 307,
              "column": 3
            },
            {
              "title": "Layout button present on nest screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1287,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:14.431Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-179fbaafd55252cf489b",
              "file": "printing.spec.js",
              "line": 312,
              "column": 3
            },
            {
              "title": "Compute button pulses when items exist",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1244,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:15.733Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-6aff064d370b9278838f",
              "file": "printing.spec.js",
              "line": 325,
              "column": 3
            }
          ]
        },
        {
          "title": "Print Nest — 2D Lane Nesting",
          "file": "printing.spec.js",
          "line": 337,
          "column": 6,
          "specs": [
            {
              "title": "demo button loads 4 items",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1160,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:22.983Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-30dfdd4a3bca0080ff75",
              "file": "printing.spec.js",
              "line": 357,
              "column": 3
            },
            {
              "title": "inline SVG visual rendered on nest screen after compute",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1297,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:24.164Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-1535e75418eb1ef8e7eb",
              "file": "printing.spec.js",
              "line": 363,
              "column": 3
            },
            {
              "title": "2-lane nesting: 2+3ft items on 5ft roll show savings in summary",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1435,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:25.479Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-d50109f81859a164bbe5",
              "file": "printing.spec.js",
              "line": 370,
              "column": 3
            },
            {
              "title": "2-lane: waste rectangle present in SVG for shorter lane",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1466,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:26.929Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-bbfb133f464368871f54",
              "file": "printing.spec.js",
              "line": 381,
              "column": 3
            },
            {
              "title": "linear group (same-width items) shows no lane savings",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1473,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:28.416Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-b3c0008d24b96e8d8ef7",
              "file": "printing.spec.js",
              "line": 392,
              "column": 3
            },
            {
              "title": "hover popup present in DOM",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1393,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:29.906Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-545e7fdfc45fbf178d83",
              "file": "printing.spec.js",
              "line": 402,
              "column": 3
            },
            {
              "title": "text plan toggle button shows/hides details",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1375,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:31.322Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-093260cd31123b5f69b0",
              "file": "printing.spec.js",
              "line": 409,
              "column": 3
            },
            {
              "title": "demo items produce 2-lane nesting for both Flex and Vinyl",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1239,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:32.716Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-6d30c45862d58ba37083",
              "file": "printing.spec.js",
              "line": 420,
              "column": 3
            },
            {
              "title": "demo button loads 4 items",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1154,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:16.992Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-e01bd768b2389a308143",
              "file": "printing.spec.js",
              "line": 357,
              "column": 3
            },
            {
              "title": "inline SVG visual rendered on nest screen after compute",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1273,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:18.167Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-0ca335967ff9529d5547",
              "file": "printing.spec.js",
              "line": 363,
              "column": 3
            },
            {
              "title": "2-lane nesting: 2+3ft items on 5ft roll show savings in summary",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1516,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:19.455Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-e5ed090ec3c7fbef93cf",
              "file": "printing.spec.js",
              "line": 370,
              "column": 3
            },
            {
              "title": "2-lane: waste rectangle present in SVG for shorter lane",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1532,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:20.986Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-a3c1f9fda5e8449d76d9",
              "file": "printing.spec.js",
              "line": 381,
              "column": 3
            },
            {
              "title": "linear group (same-width items) shows no lane savings",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1419,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:22.535Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-6d46fcb4997f61ac8173",
              "file": "printing.spec.js",
              "line": 392,
              "column": 3
            },
            {
              "title": "hover popup present in DOM",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1259,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:23.971Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-f18abd3cf660ab61f5bb",
              "file": "printing.spec.js",
              "line": 402,
              "column": 3
            },
            {
              "title": "text plan toggle button shows/hides details",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1420,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:25.250Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-028251705e43e78d29c5",
              "file": "printing.spec.js",
              "line": 409,
              "column": 3
            },
            {
              "title": "demo items produce 2-lane nesting for both Flex and Vinyl",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1295,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:26.685Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-a7e7ae131d8b42c53bce",
              "file": "printing.spec.js",
              "line": 420,
              "column": 3
            }
          ]
        },
        {
          "title": "Print Nest — Runs Screen",
          "file": "printing.spec.js",
          "line": 434,
          "column": 6,
          "specs": [
            {
              "title": "runs screen renders (empty or list)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3119,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:33.971Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-3b25031229719607eb5b",
              "file": "printing.spec.js",
              "line": 443,
              "column": 3
            },
            {
              "title": "filter bar shows all 5 filter buttons",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1185,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:37.118Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-56b7969aaceb925add18",
              "file": "printing.spec.js",
              "line": 450,
              "column": 3
            },
            {
              "title": "\"All\" filter is active by default",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1110,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:38.337Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-c9cb08940ea8b6185118",
              "file": "printing.spec.js",
              "line": 460,
              "column": 3
            },
            {
              "title": "clicking a filter button makes it active and deactivates others",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1187,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:39.468Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-435850aac22d8ef6d082",
              "file": "printing.spec.js",
              "line": 464,
              "column": 3
            },
            {
              "title": "clicking Planned filter reloads runs-body",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3200,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:40.677Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-27b361306b2c7060ae14",
              "file": "printing.spec.js",
              "line": 471,
              "column": 3
            },
            {
              "title": "runs screen renders (empty or list)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3154,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:27.998Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-7abf91c4d08d19b26e24",
              "file": "printing.spec.js",
              "line": 443,
              "column": 3
            },
            {
              "title": "filter bar shows all 5 filter buttons",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1133,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:31.165Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-6103064ec5e5d704465f",
              "file": "printing.spec.js",
              "line": 450,
              "column": 3
            },
            {
              "title": "\"All\" filter is active by default",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1120,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:32.318Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-47f2c1728fbdc72349f8",
              "file": "printing.spec.js",
              "line": 460,
              "column": 3
            },
            {
              "title": "clicking a filter button makes it active and deactivates others",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1151,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:33.452Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-2429fa665e9ea707305d",
              "file": "printing.spec.js",
              "line": 464,
              "column": 3
            },
            {
              "title": "clicking Planned filter reloads runs-body",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3136,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:34.619Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-4ed10d688b2cc21447f5",
              "file": "printing.spec.js",
              "line": 471,
              "column": 3
            }
          ]
        },
        {
          "title": "Print Nest — Cancel Run UI",
          "file": "printing.spec.js",
          "line": 481,
          "column": 6,
          "specs": [
            {
              "title": "cancel button element exists in run modal",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1071,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:43.895Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-8a867334f327fe45ec88",
              "file": "printing.spec.js",
              "line": 489,
              "column": 3
            },
            {
              "title": "cancel button starts hidden when modal is closed",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1116,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:44.980Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-ab43af209b59593bf292",
              "file": "printing.spec.js",
              "line": 494,
              "column": 3
            },
            {
              "title": "cancel button shown for planned run, hidden for terminal status",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4248,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:46.120Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-5fdd7cfaffff81cfc942",
              "file": "printing.spec.js",
              "line": 499,
              "column": 3
            },
            {
              "title": "cancel button element exists in run modal",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1088,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:37.777Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-749e66b94839ef221205",
              "file": "printing.spec.js",
              "line": 489,
              "column": 3
            },
            {
              "title": "cancel button starts hidden when modal is closed",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1104,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:38.882Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-3250c4f16befeecadab2",
              "file": "printing.spec.js",
              "line": 494,
              "column": 3
            },
            {
              "title": "cancel button shown for planned run, hidden for terminal status",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4264,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:40.010Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-081a53b0845897d33401",
              "file": "printing.spec.js",
              "line": 499,
              "column": 3
            }
          ]
        },
        {
          "title": "Print Nest — Stock Screen",
          "file": "printing.spec.js",
          "line": 517,
          "column": 6,
          "specs": [
            {
              "title": "stock screen loads without error",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4166,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:50.390Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-13941d521046b534329f",
              "file": "printing.spec.js",
              "line": 526,
              "column": 3
            },
            {
              "title": "stock cards render with name and quantity",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4111,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:54.583Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-b8e987c94e632bdbfbb1",
              "file": "printing.spec.js",
              "line": 533,
              "column": 3
            },
            {
              "title": "stock screen loads without error",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4147,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:44.289Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-37ecc299bb497c26af4f",
              "file": "printing.spec.js",
              "line": 526,
              "column": 3
            },
            {
              "title": "stock cards render with name and quantity",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4149,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:48.448Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "84f3bc84c047e9f9c0b8-b46fc8519fc0281dc3ee",
              "file": "printing.spec.js",
              "line": 533,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "production.spec.js",
      "file": "production.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Production — Smoke",
          "file": "production.spec.js",
          "line": 9,
          "column": 6,
          "specs": [
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1176,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:58.722Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e03a4b7a6378dd18a075-08ab67d2b70bac9026c0",
              "file": "production.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 175,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:27:59.913Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e03a4b7a6378dd18a075-16d4c3afa38c0afcaa8a",
              "file": "production.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 238,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:00.107Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e03a4b7a6378dd18a075-479a33c51e1e0ed8ae0d",
              "file": "production.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1184,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:52.619Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e03a4b7a6378dd18a075-a5a5a553947fd21ca24d",
              "file": "production.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 196,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:53.825Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e03a4b7a6378dd18a075-629b90f96f7227a10615",
              "file": "production.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 203,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:54.037Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e03a4b7a6378dd18a075-e071e46e4761707c8728",
              "file": "production.spec.js",
              "line": 22,
              "column": 3
            }
          ]
        },
        {
          "title": "Production — HTML Structure",
          "file": "production.spec.js",
          "line": 29,
          "column": 6,
          "specs": [
            {
              "title": "all screens present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:00.367Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e03a4b7a6378dd18a075-751b1956f8285092c40a",
              "file": "production.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub auth gate and navigation present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:00.441Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e03a4b7a6378dd18a075-ff61111594cce2e575f7",
              "file": "production.spec.js",
              "line": 47,
              "column": 3
            },
            {
              "title": "all screens present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:54.259Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e03a4b7a6378dd18a075-aef8b5e752aa2910905d",
              "file": "production.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub auth gate and navigation present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:54.298Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e03a4b7a6378dd18a075-6f38512f94362937af87",
              "file": "production.spec.js",
              "line": 47,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "recce_client.spec.js",
      "file": "recce_client.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Phase 4.1 — DB schema present",
          "file": "recce_client.spec.js",
          "line": 29,
          "column": 6,
          "specs": [
            {
              "title": "PostgREST exposes client + client_visible_recces_v + mail_outbox",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 55,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:00.505Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-2eb686f95b6e6f5196d9",
              "file": "recce_client.spec.js",
              "line": 30,
              "column": 3
            },
            {
              "title": "sensitive tables (client_user, client_session, client_invite) are NOT directly readable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 45,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:00.577Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-465ce260ba7e6bd0fa43",
              "file": "recce_client.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "PostgREST exposes client + client_visible_recces_v + mail_outbox",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 43,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:54.358Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-2ce0aac8156834b76b73",
              "file": "recce_client.spec.js",
              "line": 30,
              "column": 3
            },
            {
              "title": "sensitive tables (client_user, client_session, client_invite) are NOT directly readable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 35,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:54.410Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-75a392213d756dc1a942",
              "file": "recce_client.spec.js",
              "line": 39,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.1 — supervisor gate",
          "file": "recce_client.spec.js",
          "line": 51,
          "column": 6,
          "specs": [
            {
              "title": "non-supervisor invite attempt returns forbidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 488,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 17,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:61:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 61
                        },
                        "snippet": "  59 |       p_invited_by: 'mukesh',\n  60 |     });\n> 61 |     expect(j.ok).toBe(false);\n     |                  ^\n  62 |     expect(j.error).toBe('forbidden');\n  63 |     await ctx.dispose();\n  64 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  59 |       p_invited_by: 'mukesh',\n  60 |     });\n> 61 |     expect(j.ok).toBe(false);\n     |                  ^\n  62 |     expect(j.error).toBe('forbidden');\n  63 |     await ctx.dispose();\n  64 |   });\n    at /var/www/360lm/tests/recce_client.spec.js:61:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:00.637Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-s-67b86-e-attempt-returns-forbidden-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 61
                      }
                    },
                    {
                      "workerIndex": 489,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 81,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:61:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 61
                        },
                        "snippet": "  59 |       p_invited_by: 'mukesh',\n  60 |     });\n> 61 |     expect(j.ok).toBe(false);\n     |                  ^\n  62 |     expect(j.error).toBe('forbidden');\n  63 |     await ctx.dispose();\n  64 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  59 |       p_invited_by: 'mukesh',\n  60 |     });\n> 61 |     expect(j.ok).toBe(false);\n     |                  ^\n  62 |     expect(j.error).toBe('forbidden');\n  63 |     await ctx.dispose();\n  64 |   });\n    at /var/www/360lm/tests/recce_client.spec.js:61:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:28:02.366Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-s-67b86-e-attempt-returns-forbidden-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 61
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-ee86b5d78885b6d436ec",
              "file": "recce_client.spec.js",
              "line": 52,
              "column": 3
            },
            {
              "title": "non-supervisor invite attempt returns forbidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1495,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 15,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:61:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 61
                        },
                        "snippet": "  59 |       p_invited_by: 'mukesh',\n  60 |     });\n> 61 |     expect(j.ok).toBe(false);\n     |                  ^\n  62 |     expect(j.error).toBe('forbidden');\n  63 |     await ctx.dispose();\n  64 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  59 |       p_invited_by: 'mukesh',\n  60 |     });\n> 61 |     expect(j.ok).toBe(false);\n     |                  ^\n  62 |     expect(j.error).toBe('forbidden');\n  63 |     await ctx.dispose();\n  64 |   });\n    at /var/www/360lm/tests/recce_client.spec.js:61:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:54.457Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-s-67b86-e-attempt-returns-forbidden-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 61
                      }
                    },
                    {
                      "workerIndex": 1496,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 76,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:61:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 61
                        },
                        "snippet": "  59 |       p_invited_by: 'mukesh',\n  60 |     });\n> 61 |     expect(j.ok).toBe(false);\n     |                  ^\n  62 |     expect(j.error).toBe('forbidden');\n  63 |     await ctx.dispose();\n  64 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  59 |       p_invited_by: 'mukesh',\n  60 |     });\n> 61 |     expect(j.ok).toBe(false);\n     |                  ^\n  62 |     expect(j.error).toBe('forbidden');\n  63 |     await ctx.dispose();\n  64 |   });\n    at /var/www/360lm/tests/recce_client.spec.js:61:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:27:55.480Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-s-67b86-e-attempt-returns-forbidden-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 61
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-39a2c0b302b1b2681d65",
              "file": "recce_client.spec.js",
              "line": 52,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.1 — end-to-end auth + visibility",
          "file": "recce_client.spec.js",
          "line": 68,
          "column": 6,
          "specs": [
            {
              "title": "invite → consume → validate → recces_for_client_user → revoke",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 490,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 289,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:108:23",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 23,
                          "line": 108
                        },
                        "snippet": "  106 |         p_invited_by: 'harish',\n  107 |       });\n> 108 |       expect(invR.ok).toBe(true);\n      |                       ^\n  109 |       expect(invR.invite_id).toBeTruthy();\n  110 |       expect(invR.client_user_id).toBeTruthy();\n  111 |       const inviteId = invR.invite_id;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 23,
                            "line": 108
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  106 |         p_invited_by: 'harish',\n  107 |       });\n> 108 |       expect(invR.ok).toBe(true);\n      |                       ^\n  109 |       expect(invR.invite_id).toBeTruthy();\n  110 |       expect(invR.client_user_id).toBeTruthy();\n  111 |       const inviteId = invR.invite_id;\n    at /var/www/360lm/tests/recce_client.spec.js:108:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:03.680Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-e-3ef88-es-for-client-user-→-revoke-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 23,
                        "line": 108
                      }
                    },
                    {
                      "workerIndex": 491,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 239,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:108:23",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 23,
                          "line": 108
                        },
                        "snippet": "  106 |         p_invited_by: 'harish',\n  107 |       });\n> 108 |       expect(invR.ok).toBe(true);\n      |                       ^\n  109 |       expect(invR.invite_id).toBeTruthy();\n  110 |       expect(invR.client_user_id).toBeTruthy();\n  111 |       const inviteId = invR.invite_id;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 23,
                            "line": 108
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  106 |         p_invited_by: 'harish',\n  107 |       });\n> 108 |       expect(invR.ok).toBe(true);\n      |                       ^\n  109 |       expect(invR.invite_id).toBeTruthy();\n  110 |       expect(invR.client_user_id).toBeTruthy();\n  111 |       const inviteId = invR.invite_id;\n    at /var/www/360lm/tests/recce_client.spec.js:108:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:28:05.117Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-e-3ef88-es-for-client-user-→-revoke-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 23,
                        "line": 108
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-546da71e4f9760dc0c40",
              "file": "recce_client.spec.js",
              "line": 69,
              "column": 3
            },
            {
              "title": "brand filter: HP client_user does NOT see Lenovo Recces",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 492,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 179,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:201:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 201
                        },
                        "snippet": "  199 |       p_display_name: 'X', p_invited_by: 'harish',\n  200 |     });\n> 201 |     expect(invR.ok).toBe(true);\n      |                     ^\n  202 |     const recces = await rpc(ctx, 'recces_for_client_user', { p_client_user_id: invR.client_user_id });\n  203 |     const subs = recces.map(r => r.sub_id).sort();\n  204 |     // Should ONLY see subHp (brand=HP_FX); subLen has brand=LEN_FX which is NOT in HP client's brand_codes"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  199 |       p_display_name: 'X', p_invited_by: 'harish',\n  200 |     });\n> 201 |     expect(invR.ok).toBe(true);\n      |                     ^\n  202 |     const recces = await rpc(ctx, 'recces_for_client_user', { p_client_user_id: invR.client_user_id });\n  203 |     const subs = recces.map(r => r.sub_id).sort();\n  204 |     // Should ONLY see subHp (brand=HP_FX); subLen has brand=LEN_FX which is NOT in HP client's brand_codes\n    at /var/www/360lm/tests/recce_client.spec.js:201:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:06.396Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-e-98fa6--does-NOT-see-Lenovo-Recces-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 201
                      }
                    },
                    {
                      "workerIndex": 493,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 163,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:201:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 201
                        },
                        "snippet": "  199 |       p_display_name: 'X', p_invited_by: 'harish',\n  200 |     });\n> 201 |     expect(invR.ok).toBe(true);\n      |                     ^\n  202 |     const recces = await rpc(ctx, 'recces_for_client_user', { p_client_user_id: invR.client_user_id });\n  203 |     const subs = recces.map(r => r.sub_id).sort();\n  204 |     // Should ONLY see subHp (brand=HP_FX); subLen has brand=LEN_FX which is NOT in HP client's brand_codes"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  199 |       p_display_name: 'X', p_invited_by: 'harish',\n  200 |     });\n> 201 |     expect(invR.ok).toBe(true);\n      |                     ^\n  202 |     const recces = await rpc(ctx, 'recces_for_client_user', { p_client_user_id: invR.client_user_id });\n  203 |     const subs = recces.map(r => r.sub_id).sort();\n  204 |     // Should ONLY see subHp (brand=HP_FX); subLen has brand=LEN_FX which is NOT in HP client's brand_codes\n    at /var/www/360lm/tests/recce_client.spec.js:201:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:28:07.559Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-e-98fa6--does-NOT-see-Lenovo-Recces-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 201
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-92620518c8f4d6f343fe",
              "file": "recce_client.spec.js",
              "line": 170,
              "column": 3
            },
            {
              "title": "invite → consume → validate → recces_for_client_user → revoke",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1497,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 202,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:108:23",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 23,
                          "line": 108
                        },
                        "snippet": "  106 |         p_invited_by: 'harish',\n  107 |       });\n> 108 |       expect(invR.ok).toBe(true);\n      |                       ^\n  109 |       expect(invR.invite_id).toBeTruthy();\n  110 |       expect(invR.client_user_id).toBeTruthy();\n  111 |       const inviteId = invR.invite_id;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 23,
                            "line": 108
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  106 |         p_invited_by: 'harish',\n  107 |       });\n> 108 |       expect(invR.ok).toBe(true);\n      |                       ^\n  109 |       expect(invR.invite_id).toBeTruthy();\n  110 |       expect(invR.client_user_id).toBeTruthy();\n  111 |       const inviteId = invR.invite_id;\n    at /var/www/360lm/tests/recce_client.spec.js:108:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:56.545Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-e-3ef88-es-for-client-user-→-revoke-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 23,
                        "line": 108
                      }
                    },
                    {
                      "workerIndex": 1498,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 193,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:108:23",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 23,
                          "line": 108
                        },
                        "snippet": "  106 |         p_invited_by: 'harish',\n  107 |       });\n> 108 |       expect(invR.ok).toBe(true);\n      |                       ^\n  109 |       expect(invR.invite_id).toBeTruthy();\n  110 |       expect(invR.client_user_id).toBeTruthy();\n  111 |       const inviteId = invR.invite_id;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 23,
                            "line": 108
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  106 |         p_invited_by: 'harish',\n  107 |       });\n> 108 |       expect(invR.ok).toBe(true);\n      |                       ^\n  109 |       expect(invR.invite_id).toBeTruthy();\n  110 |       expect(invR.client_user_id).toBeTruthy();\n  111 |       const inviteId = invR.invite_id;\n    at /var/www/360lm/tests/recce_client.spec.js:108:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:27:57.736Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-e-3ef88-es-for-client-user-→-revoke-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 23,
                        "line": 108
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-cfada63b1cb12a94dfc4",
              "file": "recce_client.spec.js",
              "line": 69,
              "column": 3
            },
            {
              "title": "brand filter: HP client_user does NOT see Lenovo Recces",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1499,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 149,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:201:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 201
                        },
                        "snippet": "  199 |       p_display_name: 'X', p_invited_by: 'harish',\n  200 |     });\n> 201 |     expect(invR.ok).toBe(true);\n      |                     ^\n  202 |     const recces = await rpc(ctx, 'recces_for_client_user', { p_client_user_id: invR.client_user_id });\n  203 |     const subs = recces.map(r => r.sub_id).sort();\n  204 |     // Should ONLY see subHp (brand=HP_FX); subLen has brand=LEN_FX which is NOT in HP client's brand_codes"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  199 |       p_display_name: 'X', p_invited_by: 'harish',\n  200 |     });\n> 201 |     expect(invR.ok).toBe(true);\n      |                     ^\n  202 |     const recces = await rpc(ctx, 'recces_for_client_user', { p_client_user_id: invR.client_user_id });\n  203 |     const subs = recces.map(r => r.sub_id).sort();\n  204 |     // Should ONLY see subHp (brand=HP_FX); subLen has brand=LEN_FX which is NOT in HP client's brand_codes\n    at /var/www/360lm/tests/recce_client.spec.js:201:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:27:58.835Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-e-98fa6--does-NOT-see-Lenovo-Recces-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 201
                      }
                    },
                    {
                      "workerIndex": 1500,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 181,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:201:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 201
                        },
                        "snippet": "  199 |       p_display_name: 'X', p_invited_by: 'harish',\n  200 |     });\n> 201 |     expect(invR.ok).toBe(true);\n      |                     ^\n  202 |     const recces = await rpc(ctx, 'recces_for_client_user', { p_client_user_id: invR.client_user_id });\n  203 |     const subs = recces.map(r => r.sub_id).sort();\n  204 |     // Should ONLY see subHp (brand=HP_FX); subLen has brand=LEN_FX which is NOT in HP client's brand_codes"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 201
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  199 |       p_display_name: 'X', p_invited_by: 'harish',\n  200 |     });\n> 201 |     expect(invR.ok).toBe(true);\n      |                     ^\n  202 |     const recces = await rpc(ctx, 'recces_for_client_user', { p_client_user_id: invR.client_user_id });\n  203 |     const subs = recces.map(r => r.sub_id).sort();\n  204 |     // Should ONLY see subHp (brand=HP_FX); subLen has brand=LEN_FX which is NOT in HP client's brand_codes\n    at /var/www/360lm/tests/recce_client.spec.js:201:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:27:59.979Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-e-98fa6--does-NOT-see-Lenovo-Recces-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 201
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-0fd992f8258e53f153d6",
              "file": "recce_client.spec.js",
              "line": 170,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.2 — PWA shell",
          "file": "recce_client.spec.js",
          "line": 220,
          "column": 6,
          "specs": [
            {
              "title": "login screen renders without session",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 494,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1251,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:08.536Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-65d9e3fb5718270df3fa",
              "file": "recce_client.spec.js",
              "line": 223,
              "column": 3
            },
            {
              "title": "SW version + manifest reachable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 494,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 64,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:09.935Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-c3c5315fc7aac68f0469",
              "file": "recce_client.spec.js",
              "line": 231,
              "column": 3
            },
            {
              "title": "Phase 4.16 — compare screen + bulk Compare button wired",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 494,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 32,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:10.021Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-cce10ac7d0e99328dba5",
              "file": "recce_client.spec.js",
              "line": 241,
              "column": 3
            },
            {
              "title": "login does not leak existence (returns ok=true for non-existent email)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 494,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2407,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:10.073Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-9c21e54f9fbbd6477563",
              "file": "recce_client.spec.js",
              "line": 253,
              "column": 3
            },
            {
              "title": "magic-link → list renders → viewer signs+opens → logout revokes",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 494,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 173,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:319:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 319
                        },
                        "snippet": "  317 |       p_display_name: 'PW42', p_invited_by: 'harish',\n  318 |     });\n> 319 |     expect(invR.ok).toBe(true);\n      |                     ^\n  320 |\n  321 |     try {\n  322 |       // Magic-link → URL"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 319
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  317 |       p_display_name: 'PW42', p_invited_by: 'harish',\n  318 |     });\n> 319 |     expect(invR.ok).toBe(true);\n      |                     ^\n  320 |\n  321 |     try {\n  322 |       // Magic-link → URL\n    at /var/www/360lm/tests/recce_client.spec.js:319:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:12.500Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-P-4eb59-igns-opens-→-logout-revokes-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-P-4eb59-igns-opens-→-logout-revokes-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 319
                      }
                    },
                    {
                      "workerIndex": 495,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 285,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:319:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 319
                        },
                        "snippet": "  317 |       p_display_name: 'PW42', p_invited_by: 'harish',\n  318 |     });\n> 319 |     expect(invR.ok).toBe(true);\n      |                     ^\n  320 |\n  321 |     try {\n  322 |       // Magic-link → URL"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 319
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  317 |       p_display_name: 'PW42', p_invited_by: 'harish',\n  318 |     });\n> 319 |     expect(invR.ok).toBe(true);\n      |                     ^\n  320 |\n  321 |     try {\n  322 |       // Magic-link → URL\n    at /var/www/360lm/tests/recce_client.spec.js:319:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:28:13.520Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-P-4eb59-igns-opens-→-logout-revokes-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-P-4eb59-igns-opens-→-logout-revokes-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 319
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-d35c41195d319c4ff459",
              "file": "recce_client.spec.js",
              "line": 265,
              "column": 3
            },
            {
              "title": "expired/used token shows error on the login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 496,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1978,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:14.754Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-2394a60373cb4e8b448c",
              "file": "recce_client.spec.js",
              "line": 373,
              "column": 3
            },
            {
              "title": "login screen renders without session",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1501,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1543,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:28:01.212Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-74d0473e431cae3c642e",
              "file": "recce_client.spec.js",
              "line": 223,
              "column": 3
            },
            {
              "title": "SW version + manifest reachable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1501,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 100,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:28:03.054Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-46eba6276c21b0191c67",
              "file": "recce_client.spec.js",
              "line": 231,
              "column": 3
            },
            {
              "title": "Phase 4.16 — compare screen + bulk Compare button wired",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1501,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 43,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:28:03.194Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-cd192c968ad6c49e096d",
              "file": "recce_client.spec.js",
              "line": 241,
              "column": 3
            },
            {
              "title": "login does not leak existence (returns ok=true for non-existent email)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1501,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2681,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:28:03.298Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-0cf92f625917f085eecd",
              "file": "recce_client.spec.js",
              "line": 253,
              "column": 3
            },
            {
              "title": "magic-link → list renders → viewer signs+opens → logout revokes",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1501,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 264,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:319:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 319
                        },
                        "snippet": "  317 |       p_display_name: 'PW42', p_invited_by: 'harish',\n  318 |     });\n> 319 |     expect(invR.ok).toBe(true);\n      |                     ^\n  320 |\n  321 |     try {\n  322 |       // Magic-link → URL"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 319
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  317 |       p_display_name: 'PW42', p_invited_by: 'harish',\n  318 |     });\n> 319 |     expect(invR.ok).toBe(true);\n      |                     ^\n  320 |\n  321 |     try {\n  322 |       // Magic-link → URL\n    at /var/www/360lm/tests/recce_client.spec.js:319:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:28:06.022Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-P-4eb59-igns-opens-→-logout-revokes-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-P-4eb59-igns-opens-→-logout-revokes-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 319
                      }
                    },
                    {
                      "workerIndex": 1502,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 354,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:319:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 319
                        },
                        "snippet": "  317 |       p_display_name: 'PW42', p_invited_by: 'harish',\n  318 |     });\n> 319 |     expect(invR.ok).toBe(true);\n      |                     ^\n  320 |\n  321 |     try {\n  322 |       // Magic-link → URL"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 319
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  317 |       p_display_name: 'PW42', p_invited_by: 'harish',\n  318 |     });\n> 319 |     expect(invR.ok).toBe(true);\n      |                     ^\n  320 |\n  321 |     try {\n  322 |       // Magic-link → URL\n    at /var/www/360lm/tests/recce_client.spec.js:319:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:28:07.434Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-P-4eb59-igns-opens-→-logout-revokes-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-P-4eb59-igns-opens-→-logout-revokes-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 319
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-ff8f74ac1db8d1de7ada",
              "file": "recce_client.spec.js",
              "line": 265,
              "column": 3
            },
            {
              "title": "expired/used token shows error on the login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1503,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1979,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:28:08.861Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-bb5cd1a4e0c08edf40e6",
              "file": "recce_client.spec.js",
              "line": 373,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.4 — client_set_response RPC",
          "file": "recce_client.spec.js",
          "line": 383,
          "column": 6,
          "specs": [
            {
              "title": "validation: bad_status / comment_required / forbidden / session_invalid",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 496,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 176,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:421:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 421
                        },
                        "snippet": "  419 |         p_session_id: sessionId, p_sub_id: subId, p_status: 'maybe',\n  420 |       });\n> 421 |       expect(r.ok).toBe(false); expect(r.error).toBe('bad_status');\n      |                    ^\n  422 |\n  423 |       // comment_required for rejected\n  424 |       r = await rpc(ctx, 'client_set_response', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 421
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  419 |         p_session_id: sessionId, p_sub_id: subId, p_status: 'maybe',\n  420 |       });\n> 421 |       expect(r.ok).toBe(false); expect(r.error).toBe('bad_status');\n      |                    ^\n  422 |\n  423 |       // comment_required for rejected\n  424 |       r = await rpc(ctx, 'client_set_response', {\n    at /var/www/360lm/tests/recce_client.spec.js:421:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:16.908Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-c-6b379-d-forbidden-session-invalid-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 421
                      }
                    },
                    {
                      "workerIndex": 497,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 192,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:421:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 421
                        },
                        "snippet": "  419 |         p_session_id: sessionId, p_sub_id: subId, p_status: 'maybe',\n  420 |       });\n> 421 |       expect(r.ok).toBe(false); expect(r.error).toBe('bad_status');\n      |                    ^\n  422 |\n  423 |       // comment_required for rejected\n  424 |       r = await rpc(ctx, 'client_set_response', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 421
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  419 |         p_session_id: sessionId, p_sub_id: subId, p_status: 'maybe',\n  420 |       });\n> 421 |       expect(r.ok).toBe(false); expect(r.error).toBe('bad_status');\n      |                    ^\n  422 |\n  423 |       // comment_required for rejected\n  424 |       r = await rpc(ctx, 'client_set_response', {\n    at /var/www/360lm/tests/recce_client.spec.js:421:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:28:17.961Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-c-6b379-d-forbidden-session-invalid-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 421
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-9ccc07f6111bdd197350",
              "file": "recce_client.spec.js",
              "line": 384,
              "column": 3
            },
            {
              "title": "validation: bad_status / comment_required / forbidden / session_invalid",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1503,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 178,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:421:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 421
                        },
                        "snippet": "  419 |         p_session_id: sessionId, p_sub_id: subId, p_status: 'maybe',\n  420 |       });\n> 421 |       expect(r.ok).toBe(false); expect(r.error).toBe('bad_status');\n      |                    ^\n  422 |\n  423 |       // comment_required for rejected\n  424 |       r = await rpc(ctx, 'client_set_response', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 421
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  419 |         p_session_id: sessionId, p_sub_id: subId, p_status: 'maybe',\n  420 |       });\n> 421 |       expect(r.ok).toBe(false); expect(r.error).toBe('bad_status');\n      |                    ^\n  422 |\n  423 |       // comment_required for rejected\n  424 |       r = await rpc(ctx, 'client_set_response', {\n    at /var/www/360lm/tests/recce_client.spec.js:421:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:28:11.004Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-c-6b379-d-forbidden-session-invalid-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 421
                      }
                    },
                    {
                      "workerIndex": 1504,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 213,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:421:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 421
                        },
                        "snippet": "  419 |         p_session_id: sessionId, p_sub_id: subId, p_status: 'maybe',\n  420 |       });\n> 421 |       expect(r.ok).toBe(false); expect(r.error).toBe('bad_status');\n      |                    ^\n  422 |\n  423 |       // comment_required for rejected\n  424 |       r = await rpc(ctx, 'client_set_response', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 421
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  419 |         p_session_id: sessionId, p_sub_id: subId, p_status: 'maybe',\n  420 |       });\n> 421 |       expect(r.ok).toBe(false); expect(r.error).toBe('bad_status');\n      |                    ^\n  422 |\n  423 |       // comment_required for rejected\n  424 |       r = await rpc(ctx, 'client_set_response', {\n    at /var/www/360lm/tests/recce_client.spec.js:421:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:28:12.076Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-c-6b379-d-forbidden-session-invalid-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 421
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-af2cc5d76f5af80dd3fc",
              "file": "recce_client.spec.js",
              "line": 384,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.4 — PWA viewer response form",
          "file": "recce_client.spec.js",
          "line": 478,
          "column": 6,
          "specs": [
            {
              "title": "initial pending state → Approve → list status updates → re-open → Approve with changes",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 498,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30112,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:28:18.947Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-P-d1b99-open-→-Approve-with-changes-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-P-d1b99-open-→-Approve-with-changes-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 499,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30098,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:28:50.110Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-P-d1b99-open-→-Approve-with-changes-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-P-d1b99-open-→-Approve-with-changes-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-2f186cf814b3b4a3d30b",
              "file": "recce_client.spec.js",
              "line": 481,
              "column": 3
            },
            {
              "title": "initial pending state → Approve → list status updates → re-open → Approve with changes",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1505,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30096,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:28:13.201Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-P-d1b99-open-→-Approve-with-changes-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-P-d1b99-open-→-Approve-with-changes-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1506,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30098,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:28:44.399Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-P-d1b99-open-→-Approve-with-changes-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-4-—-P-d1b99-open-→-Approve-with-changes-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-152bd10f2a441dd03e95",
              "file": "recce_client.spec.js",
              "line": 481,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.2 — recce-view-proxy ClientPortal auth",
          "file": "recce_client.spec.js",
          "line": 577,
          "column": 6,
          "specs": [
            {
              "title": "proxy /sign accepts ClientPortal session and rejects bogus session",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 500,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 99,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:594:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 594
                        },
                        "snippet": "  592 |     });\n  593 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n> 594 |     expect(ses.ok).toBe(true);\n      |                    ^\n  595 |\n  596 |     // Good session → 200\n  597 |     const good = await ctx.post('https://dev.srv1111289.hstgr.cloud/recce-view-proxy/sign', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 594
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  592 |     });\n  593 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n> 594 |     expect(ses.ok).toBe(true);\n      |                    ^\n  595 |\n  596 |     // Good session → 200\n  597 |     const good = await ctx.post('https://dev.srv1111289.hstgr.cloud/recce-view-proxy/sign', {\n    at /var/www/360lm/tests/recce_client.spec.js:594:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:21.243Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-r-c3eea-n-and-rejects-bogus-session-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 594
                      }
                    },
                    {
                      "workerIndex": 501,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 108,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:594:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 594
                        },
                        "snippet": "  592 |     });\n  593 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n> 594 |     expect(ses.ok).toBe(true);\n      |                    ^\n  595 |\n  596 |     // Good session → 200\n  597 |     const good = await ctx.post('https://dev.srv1111289.hstgr.cloud/recce-view-proxy/sign', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 594
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  592 |     });\n  593 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n> 594 |     expect(ses.ok).toBe(true);\n      |                    ^\n  595 |\n  596 |     // Good session → 200\n  597 |     const good = await ctx.post('https://dev.srv1111289.hstgr.cloud/recce-view-proxy/sign', {\n    at /var/www/360lm/tests/recce_client.spec.js:594:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:29:22.297Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-r-c3eea-n-and-rejects-bogus-session-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 594
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-759d29ff1a9acb01f947",
              "file": "recce_client.spec.js",
              "line": 578,
              "column": 3
            },
            {
              "title": "proxy /sign accepts ClientPortal session and rejects bogus session",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1507,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 97,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:594:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 594
                        },
                        "snippet": "  592 |     });\n  593 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n> 594 |     expect(ses.ok).toBe(true);\n      |                    ^\n  595 |\n  596 |     // Good session → 200\n  597 |     const good = await ctx.post('https://dev.srv1111289.hstgr.cloud/recce-view-proxy/sign', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 594
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  592 |     });\n  593 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n> 594 |     expect(ses.ok).toBe(true);\n      |                    ^\n  595 |\n  596 |     // Good session → 200\n  597 |     const good = await ctx.post('https://dev.srv1111289.hstgr.cloud/recce-view-proxy/sign', {\n    at /var/www/360lm/tests/recce_client.spec.js:594:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:15.562Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-r-c3eea-n-and-rejects-bogus-session-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 594
                      }
                    },
                    {
                      "workerIndex": 1508,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 104,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:594:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 594
                        },
                        "snippet": "  592 |     });\n  593 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n> 594 |     expect(ses.ok).toBe(true);\n      |                    ^\n  595 |\n  596 |     // Good session → 200\n  597 |     const good = await ctx.post('https://dev.srv1111289.hstgr.cloud/recce-view-proxy/sign', {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 594
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  592 |     });\n  593 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n> 594 |     expect(ses.ok).toBe(true);\n      |                    ^\n  595 |\n  596 |     // Good session → 200\n  597 |     const good = await ctx.post('https://dev.srv1111289.hstgr.cloud/recce-view-proxy/sign', {\n    at /var/www/360lm/tests/recce_client.spec.js:594:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:29:16.487Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-2-—-r-c3eea-n-and-rejects-bogus-session-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 594
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-efd5279c000d943cf03e",
              "file": "recce_client.spec.js",
              "line": 578,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.1 — mail relay worker",
          "file": "recce_client.spec.js",
          "line": 619,
          "column": 6,
          "specs": [
            {
              "title": "worker drains pending mail rows produced via the invite RPC",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 120000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 502,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 98,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:639:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 639
                        },
                        "snippet": "  637 |       p_display_name: 'Relay tester', p_invited_by: 'harish',\n  638 |     });\n> 639 |     expect(inv.ok).toBe(true);\n      |                    ^\n  640 |     const inviteId = inv.invite_id;\n  641 |\n  642 |     // Poll for the worker to mark it sent (DEV mode = console.log + mark sent)"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 639
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  637 |       p_display_name: 'Relay tester', p_invited_by: 'harish',\n  638 |     });\n> 639 |     expect(inv.ok).toBe(true);\n      |                    ^\n  640 |     const inviteId = inv.invite_id;\n  641 |\n  642 |     // Poll for the worker to mark it sent (DEV mode = console.log + mark sent)\n    at /var/www/360lm/tests/recce_client.spec.js:639:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:23.283Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-m-3f189-produced-via-the-invite-RPC-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 639
                      }
                    },
                    {
                      "workerIndex": 503,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 99,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:639:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 639
                        },
                        "snippet": "  637 |       p_display_name: 'Relay tester', p_invited_by: 'harish',\n  638 |     });\n> 639 |     expect(inv.ok).toBe(true);\n      |                    ^\n  640 |     const inviteId = inv.invite_id;\n  641 |\n  642 |     // Poll for the worker to mark it sent (DEV mode = console.log + mark sent)"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 639
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  637 |       p_display_name: 'Relay tester', p_invited_by: 'harish',\n  638 |     });\n> 639 |     expect(inv.ok).toBe(true);\n      |                    ^\n  640 |     const inviteId = inv.invite_id;\n  641 |\n  642 |     // Poll for the worker to mark it sent (DEV mode = console.log + mark sent)\n    at /var/www/360lm/tests/recce_client.spec.js:639:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:29:24.217Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-m-3f189-produced-via-the-invite-RPC-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 639
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-da75682d0441589a9324",
              "file": "recce_client.spec.js",
              "line": 620,
              "column": 3
            },
            {
              "title": "worker drains pending mail rows produced via the invite RPC",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 120000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1509,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 108,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:639:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 639
                        },
                        "snippet": "  637 |       p_display_name: 'Relay tester', p_invited_by: 'harish',\n  638 |     });\n> 639 |     expect(inv.ok).toBe(true);\n      |                    ^\n  640 |     const inviteId = inv.invite_id;\n  641 |\n  642 |     // Poll for the worker to mark it sent (DEV mode = console.log + mark sent)"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 639
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  637 |       p_display_name: 'Relay tester', p_invited_by: 'harish',\n  638 |     });\n> 639 |     expect(inv.ok).toBe(true);\n      |                    ^\n  640 |     const inviteId = inv.invite_id;\n  641 |\n  642 |     // Poll for the worker to mark it sent (DEV mode = console.log + mark sent)\n    at /var/www/360lm/tests/recce_client.spec.js:639:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:17.421Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-m-3f189-produced-via-the-invite-RPC-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 639
                      }
                    },
                    {
                      "workerIndex": 1510,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 96,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:639:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 639
                        },
                        "snippet": "  637 |       p_display_name: 'Relay tester', p_invited_by: 'harish',\n  638 |     });\n> 639 |     expect(inv.ok).toBe(true);\n      |                    ^\n  640 |     const inviteId = inv.invite_id;\n  641 |\n  642 |     // Poll for the worker to mark it sent (DEV mode = console.log + mark sent)"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 639
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  637 |       p_display_name: 'Relay tester', p_invited_by: 'harish',\n  638 |     });\n> 639 |     expect(inv.ok).toBe(true);\n      |                    ^\n  640 |     const inviteId = inv.invite_id;\n  641 |\n  642 |     // Poll for the worker to mark it sent (DEV mode = console.log + mark sent)\n    at /var/www/360lm/tests/recce_client.spec.js:639:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:29:18.456Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-1-—-m-3f189-produced-via-the-invite-RPC-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 639
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-6564155f9f9c900dabc8",
              "file": "recce_client.spec.js",
              "line": 620,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.9 — notification preferences",
          "file": "recce_client.spec.js",
          "line": 666,
          "column": 6,
          "specs": [
            {
              "title": "list_my_notify_prefs defaults all-on; set_my_notify_pref toggles; dispatch_notify suppresses when opted out",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 504,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 93,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:685:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 685
                        },
                        "snippet": "  683 |       }),\n  684 |     })).json();\n> 685 |     expect(inv.ok).toBe(true);\n      |                    ^\n  686 |     const cuId = inv.client_user_id;\n  687 |     const inviteId = inv.invite_id;\n  688 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 685
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  683 |       }),\n  684 |     })).json();\n> 685 |     expect(inv.ok).toBe(true);\n      |                    ^\n  686 |     const cuId = inv.client_user_id;\n  687 |     const inviteId = inv.invite_id;\n  688 |\n    at /var/www/360lm/tests/recce_client.spec.js:685:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:25.169Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-9-—-n-a175f-y-suppresses-when-opted-out-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 685
                      }
                    },
                    {
                      "workerIndex": 505,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 92,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:685:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 685
                        },
                        "snippet": "  683 |       }),\n  684 |     })).json();\n> 685 |     expect(inv.ok).toBe(true);\n      |                    ^\n  686 |     const cuId = inv.client_user_id;\n  687 |     const inviteId = inv.invite_id;\n  688 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 685
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  683 |       }),\n  684 |     })).json();\n> 685 |     expect(inv.ok).toBe(true);\n      |                    ^\n  686 |     const cuId = inv.client_user_id;\n  687 |     const inviteId = inv.invite_id;\n  688 |\n    at /var/www/360lm/tests/recce_client.spec.js:685:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:29:26.096Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-9-—-n-a175f-y-suppresses-when-opted-out-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 685
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-0605e0a74bdee130d1af",
              "file": "recce_client.spec.js",
              "line": 667,
              "column": 3
            },
            {
              "title": "list_my_notify_prefs defaults all-on; set_my_notify_pref toggles; dispatch_notify suppresses when opted out",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1511,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 85,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:685:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 685
                        },
                        "snippet": "  683 |       }),\n  684 |     })).json();\n> 685 |     expect(inv.ok).toBe(true);\n      |                    ^\n  686 |     const cuId = inv.client_user_id;\n  687 |     const inviteId = inv.invite_id;\n  688 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 685
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  683 |       }),\n  684 |     })).json();\n> 685 |     expect(inv.ok).toBe(true);\n      |                    ^\n  686 |     const cuId = inv.client_user_id;\n  687 |     const inviteId = inv.invite_id;\n  688 |\n    at /var/www/360lm/tests/recce_client.spec.js:685:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:19.438Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-9-—-n-a175f-y-suppresses-when-opted-out-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 685
                      }
                    },
                    {
                      "workerIndex": 1512,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 113,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:685:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 685
                        },
                        "snippet": "  683 |       }),\n  684 |     })).json();\n> 685 |     expect(inv.ok).toBe(true);\n      |                    ^\n  686 |     const cuId = inv.client_user_id;\n  687 |     const inviteId = inv.invite_id;\n  688 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 685
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  683 |       }),\n  684 |     })).json();\n> 685 |     expect(inv.ok).toBe(true);\n      |                    ^\n  686 |     const cuId = inv.client_user_id;\n  687 |     const inviteId = inv.invite_id;\n  688 |\n    at /var/www/360lm/tests/recce_client.spec.js:685:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:29:20.386Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-9-—-n-a175f-y-suppresses-when-opted-out-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 685
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-a7dfc39ea0adfe145457",
              "file": "recce_client.spec.js",
              "line": 667,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.10 — bulk response RPC",
          "file": "recce_client.spec.js",
          "line": 813,
          "column": 6,
          "specs": [
            {
              "title": "client_bulk_response: validation + partial visibility + N audit rows + N trigger fires",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 506,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 205,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n    at /var/www/360lm/tests/recce_client.spec.js:873:19",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 19,
                          "line": 873
                        },
                        "snippet": "  871 |       }),\n  872 |     })).json();\n> 873 |     expect(empty).toEqual({ ok: false, error: 'empty_sub_ids' });\n      |                   ^\n  874 |\n  875 |     // 2. validation: bad status\n  876 |     const badSt = await (await ctx.post(`${DB}/rpc/client_bulk_response`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 19,
                            "line": 873
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n\n  871 |       }),\n  872 |     })).json();\n> 873 |     expect(empty).toEqual({ ok: false, error: 'empty_sub_ids' });\n      |                   ^\n  874 |\n  875 |     // 2. validation: bad status\n  876 |     const badSt = await (await ctx.post(`${DB}/rpc/client_bulk_response`, { headers: HC,\n    at /var/www/360lm/tests/recce_client.spec.js:873:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:27.010Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-10-—--319bb--audit-rows-N-trigger-fires-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 19,
                        "line": 873
                      }
                    },
                    {
                      "workerIndex": 507,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 207,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n    at /var/www/360lm/tests/recce_client.spec.js:873:19",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 19,
                          "line": 873
                        },
                        "snippet": "  871 |       }),\n  872 |     })).json();\n> 873 |     expect(empty).toEqual({ ok: false, error: 'empty_sub_ids' });\n      |                   ^\n  874 |\n  875 |     // 2. validation: bad status\n  876 |     const badSt = await (await ctx.post(`${DB}/rpc/client_bulk_response`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 19,
                            "line": 873
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n\n  871 |       }),\n  872 |     })).json();\n> 873 |     expect(empty).toEqual({ ok: false, error: 'empty_sub_ids' });\n      |                   ^\n  874 |\n  875 |     // 2. validation: bad status\n  876 |     const badSt = await (await ctx.post(`${DB}/rpc/client_bulk_response`, { headers: HC,\n    at /var/www/360lm/tests/recce_client.spec.js:873:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:29:28.014Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-10-—--319bb--audit-rows-N-trigger-fires-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 19,
                        "line": 873
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-282e2760b0a2be924687",
              "file": "recce_client.spec.js",
              "line": 814,
              "column": 3
            },
            {
              "title": "client_bulk_response: validation + partial visibility + N audit rows + N trigger fires",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1513,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 196,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n    at /var/www/360lm/tests/recce_client.spec.js:873:19",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 19,
                          "line": 873
                        },
                        "snippet": "  871 |       }),\n  872 |     })).json();\n> 873 |     expect(empty).toEqual({ ok: false, error: 'empty_sub_ids' });\n      |                   ^\n  874 |\n  875 |     // 2. validation: bad status\n  876 |     const badSt = await (await ctx.post(`${DB}/rpc/client_bulk_response`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 19,
                            "line": 873
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n\n  871 |       }),\n  872 |     })).json();\n> 873 |     expect(empty).toEqual({ ok: false, error: 'empty_sub_ids' });\n      |                   ^\n  874 |\n  875 |     // 2. validation: bad status\n  876 |     const badSt = await (await ctx.post(`${DB}/rpc/client_bulk_response`, { headers: HC,\n    at /var/www/360lm/tests/recce_client.spec.js:873:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:21.375Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-10-—--319bb--audit-rows-N-trigger-fires-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 19,
                        "line": 873
                      }
                    },
                    {
                      "workerIndex": 1514,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 213,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n    at /var/www/360lm/tests/recce_client.spec.js:873:19",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 19,
                          "line": 873
                        },
                        "snippet": "  871 |       }),\n  872 |     })).json();\n> 873 |     expect(empty).toEqual({ ok: false, error: 'empty_sub_ids' });\n      |                   ^\n  874 |\n  875 |     // 2. validation: bad status\n  876 |     const badSt = await (await ctx.post(`${DB}/rpc/client_bulk_response`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 19,
                            "line": 873
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // deep equality\u001b[22m\n\n\u001b[32m- Expected  - 2\u001b[39m\n\u001b[31m+ Received  + 4\u001b[39m\n\n\u001b[2m  Object {\u001b[22m\n\u001b[32m-   \"error\": \"empty_sub_ids\",\u001b[39m\n\u001b[32m-   \"ok\": false,\u001b[39m\n\u001b[31m+   \"code\": \"PGRST202\",\u001b[39m\n\u001b[31m+   \"details\": \"Searched for the function counters.client_bulk_response with parameters p_comment, p_status, p_sub_ids or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\",\u001b[39m\n\u001b[31m+   \"hint\": \"Perhaps you meant to call the function counters.client_bulk_response(p_comment, p_session_id, p_status, p_sub_ids)\",\u001b[39m\n\u001b[31m+   \"message\": \"Could not find the function counters.client_bulk_response(p_comment, p_status, p_sub_ids) in the schema cache\",\u001b[39m\n\u001b[2m  }\u001b[22m\n\n  871 |       }),\n  872 |     })).json();\n> 873 |     expect(empty).toEqual({ ok: false, error: 'empty_sub_ids' });\n      |                   ^\n  874 |\n  875 |     // 2. validation: bad status\n  876 |     const badSt = await (await ctx.post(`${DB}/rpc/client_bulk_response`, { headers: HC,\n    at /var/www/360lm/tests/recce_client.spec.js:873:19"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:29:22.444Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-10-—--319bb--audit-rows-N-trigger-fires-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 19,
                        "line": 873
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-ade3092e7b584a909b75",
              "file": "recce_client.spec.js",
              "line": 814,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.11 — suspicious-login detection",
          "file": "recce_client.spec.js",
          "line": 964,
          "column": 6,
          "specs": [
            {
              "title": "consume_invite classifies risk: first=low, same IP class+UA family=low, new IP+UA=high; supervisor notify for medium/high only",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 508,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 102,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:998:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 998
                        },
                        "snippet": "   996 |       }),\n   997 |     })).json();\n>  998 |     expect(a.ok).toBe(true);\n       |                  ^\n   999 |     expect(a.login_risk).toBe('low');\n  1000 |\n  1001 |     // Login 2: same /24 + same Chrome family (version bumped) → low"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 998
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n   996 |       }),\n   997 |     })).json();\n>  998 |     expect(a.ok).toBe(true);\n       |                  ^\n   999 |     expect(a.login_risk).toBe('low');\n  1000 |\n  1001 |     // Login 2: same /24 + same Chrome family (version bumped) → low\n    at /var/www/360lm/tests/recce_client.spec.js:998:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:29.174Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-11-—--67551-notify-for-medium-high-only-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 998
                      }
                    },
                    {
                      "workerIndex": 509,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 96,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:998:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 998
                        },
                        "snippet": "   996 |       }),\n   997 |     })).json();\n>  998 |     expect(a.ok).toBe(true);\n       |                  ^\n   999 |     expect(a.login_risk).toBe('low');\n  1000 |\n  1001 |     // Login 2: same /24 + same Chrome family (version bumped) → low"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 998
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n   996 |       }),\n   997 |     })).json();\n>  998 |     expect(a.ok).toBe(true);\n       |                  ^\n   999 |     expect(a.login_risk).toBe('low');\n  1000 |\n  1001 |     // Login 2: same /24 + same Chrome family (version bumped) → low\n    at /var/www/360lm/tests/recce_client.spec.js:998:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:29:30.059Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-11-—--67551-notify-for-medium-high-only-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 998
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-784aadc42f8d903fc7b0",
              "file": "recce_client.spec.js",
              "line": 965,
              "column": 3
            },
            {
              "title": "consume_invite classifies risk: first=low, same IP class+UA family=low, new IP+UA=high; supervisor notify for medium/high only",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1515,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 96,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:998:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 998
                        },
                        "snippet": "   996 |       }),\n   997 |     })).json();\n>  998 |     expect(a.ok).toBe(true);\n       |                  ^\n   999 |     expect(a.login_risk).toBe('low');\n  1000 |\n  1001 |     // Login 2: same /24 + same Chrome family (version bumped) → low"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 998
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n   996 |       }),\n   997 |     })).json();\n>  998 |     expect(a.ok).toBe(true);\n       |                  ^\n   999 |     expect(a.login_risk).toBe('low');\n  1000 |\n  1001 |     // Login 2: same /24 + same Chrome family (version bumped) → low\n    at /var/www/360lm/tests/recce_client.spec.js:998:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:23.524Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-11-—--67551-notify-for-medium-high-only-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 998
                      }
                    },
                    {
                      "workerIndex": 1516,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 109,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:998:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 998
                        },
                        "snippet": "   996 |       }),\n   997 |     })).json();\n>  998 |     expect(a.ok).toBe(true);\n       |                  ^\n   999 |     expect(a.login_risk).toBe('low');\n  1000 |\n  1001 |     // Login 2: same /24 + same Chrome family (version bumped) → low"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 998
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n   996 |       }),\n   997 |     })).json();\n>  998 |     expect(a.ok).toBe(true);\n       |                  ^\n   999 |     expect(a.login_risk).toBe('low');\n  1000 |\n  1001 |     // Login 2: same /24 + same Chrome family (version bumped) → low\n    at /var/www/360lm/tests/recce_client.spec.js:998:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:29:24.449Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-11-—--67551-notify-for-medium-high-only-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 998
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-5b405a9424d3bc684bad",
              "file": "recce_client.spec.js",
              "line": 965,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.13 — daily digest emails",
          "file": "recce_client.spec.js",
          "line": 1079,
          "column": 6,
          "specs": [
            {
              "title": "mode picker: instant default, daily defers + flush makes one mail, off suppresses, bad_mode rejected",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 510,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 86,
                      "error": {
                        "message": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m",
                        "stack": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1112:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 1112
                        },
                        "snippet": "  1110 |       data: JSON.stringify({ p_session_id: sessionId }),\n  1111 |     })).json();\n> 1112 |     expect(list).toHaveLength(2);\n       |                  ^\n  1113 |     expect(list.every(r => r.mode === 'instant')).toBe(true);\n  1114 |     expect(list.every(r => r.enabled === true)).toBe(true);\n  1115 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 1112
                          },
                          "message": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m\n\n  1110 |       data: JSON.stringify({ p_session_id: sessionId }),\n  1111 |     })).json();\n> 1112 |     expect(list).toHaveLength(2);\n       |                  ^\n  1113 |     expect(list.every(r => r.mode === 'instant')).toBe(true);\n  1114 |     expect(list.every(r => r.enabled === true)).toBe(true);\n  1115 |\n    at /var/www/360lm/tests/recce_client.spec.js:1112:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:30.961Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-13-—--9b580-uppresses-bad-mode-rejected-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 1112
                      }
                    },
                    {
                      "workerIndex": 511,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 88,
                      "error": {
                        "message": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m",
                        "stack": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1112:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 1112
                        },
                        "snippet": "  1110 |       data: JSON.stringify({ p_session_id: sessionId }),\n  1111 |     })).json();\n> 1112 |     expect(list).toHaveLength(2);\n       |                  ^\n  1113 |     expect(list.every(r => r.mode === 'instant')).toBe(true);\n  1114 |     expect(list.every(r => r.enabled === true)).toBe(true);\n  1115 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 1112
                          },
                          "message": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m\n\n  1110 |       data: JSON.stringify({ p_session_id: sessionId }),\n  1111 |     })).json();\n> 1112 |     expect(list).toHaveLength(2);\n       |                  ^\n  1113 |     expect(list.every(r => r.mode === 'instant')).toBe(true);\n  1114 |     expect(list.every(r => r.enabled === true)).toBe(true);\n  1115 |\n    at /var/www/360lm/tests/recce_client.spec.js:1112:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:29:31.877Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-13-—--9b580-uppresses-bad-mode-rejected-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 1112
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-212f6cf84db9cee8333e",
              "file": "recce_client.spec.js",
              "line": 1080,
              "column": 3
            },
            {
              "title": "mode picker: instant default, daily defers + flush makes one mail, off suppresses, bad_mode rejected",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1517,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 104,
                      "error": {
                        "message": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m",
                        "stack": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1112:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 1112
                        },
                        "snippet": "  1110 |       data: JSON.stringify({ p_session_id: sessionId }),\n  1111 |     })).json();\n> 1112 |     expect(list).toHaveLength(2);\n       |                  ^\n  1113 |     expect(list.every(r => r.mode === 'instant')).toBe(true);\n  1114 |     expect(list.every(r => r.enabled === true)).toBe(true);\n  1115 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 1112
                          },
                          "message": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m\n\n  1110 |       data: JSON.stringify({ p_session_id: sessionId }),\n  1111 |     })).json();\n> 1112 |     expect(list).toHaveLength(2);\n       |                  ^\n  1113 |     expect(list.every(r => r.mode === 'instant')).toBe(true);\n  1114 |     expect(list.every(r => r.enabled === true)).toBe(true);\n  1115 |\n    at /var/www/360lm/tests/recce_client.spec.js:1112:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:25.499Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-13-—--9b580-uppresses-bad-mode-rejected-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 1112
                      }
                    },
                    {
                      "workerIndex": 1518,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 96,
                      "error": {
                        "message": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m",
                        "stack": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1112:18",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 18,
                          "line": 1112
                        },
                        "snippet": "  1110 |       data: JSON.stringify({ p_session_id: sessionId }),\n  1111 |     })).json();\n> 1112 |     expect(list).toHaveLength(2);\n       |                  ^\n  1113 |     expect(list.every(r => r.mode === 'instant')).toBe(true);\n  1114 |     expect(list.every(r => r.enabled === true)).toBe(true);\n  1115 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 18,
                            "line": 1112
                          },
                          "message": "TypeError: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoHaveLength\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must have a length property whose value must be a number\n\nReceived has type:  object\nReceived has value: \u001b[31m{\"code\": \"PGRST202\", \"details\": \"Searched for the function counters.list_my_notify_prefs without parameters or with a single unnamed json/jsonb parameter, but no matches were found in the schema cache.\", \"hint\": null, \"message\": \"Could not find the function counters.list_my_notify_prefs without parameters in the schema cache\"}\u001b[39m\n\n  1110 |       data: JSON.stringify({ p_session_id: sessionId }),\n  1111 |     })).json();\n> 1112 |     expect(list).toHaveLength(2);\n       |                  ^\n  1113 |     expect(list.every(r => r.mode === 'instant')).toBe(true);\n  1114 |     expect(list.every(r => r.enabled === true)).toBe(true);\n  1115 |\n    at /var/www/360lm/tests/recce_client.spec.js:1112:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:29:26.514Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-13-—--9b580-uppresses-bad-mode-rejected-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 18,
                        "line": 1112
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-ed4708b7ac8a28fd315e",
              "file": "recce_client.spec.js",
              "line": 1080,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4.18 — self-serve fresh magic-link audit",
          "file": "recce_client.spec.js",
          "line": 1262,
          "column": 6,
          "specs": [
            {
              "title": "request_client_login: real email writes audit + mail + invite; unknown silent; rate-limit silent; admin_list_invites by user",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 512,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1281:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 1281
                        },
                        "snippet": "  1279 |       }),\n  1280 |     })).json();\n> 1281 |     expect(seed.ok).toBe(true);\n       |                     ^\n  1282 |     const cuId = seed.client_user_id;\n  1283 |\n  1284 |     const baseAudit = await (await ctx.post(`${DB}/rpc/admin_list_audit`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 1281
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1279 |       }),\n  1280 |     })).json();\n> 1281 |     expect(seed.ok).toBe(true);\n       |                     ^\n  1282 |     const cuId = seed.client_user_id;\n  1283 |\n  1284 |     const baseAudit = await (await ctx.post(`${DB}/rpc/admin_list_audit`, { headers: HC,\n    at /var/www/360lm/tests/recce_client.spec.js:1281:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:32.766Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-18-—--c6d2b--admin-list-invites-by-user-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 1281
                      }
                    },
                    {
                      "workerIndex": 513,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 65,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1281:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 1281
                        },
                        "snippet": "  1279 |       }),\n  1280 |     })).json();\n> 1281 |     expect(seed.ok).toBe(true);\n       |                     ^\n  1282 |     const cuId = seed.client_user_id;\n  1283 |\n  1284 |     const baseAudit = await (await ctx.post(`${DB}/rpc/admin_list_audit`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 1281
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1279 |       }),\n  1280 |     })).json();\n> 1281 |     expect(seed.ok).toBe(true);\n       |                     ^\n  1282 |     const cuId = seed.client_user_id;\n  1283 |\n  1284 |     const baseAudit = await (await ctx.post(`${DB}/rpc/admin_list_audit`, { headers: HC,\n    at /var/www/360lm/tests/recce_client.spec.js:1281:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:29:33.662Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-18-—--c6d2b--admin-list-invites-by-user-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 1281
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-0e70e9dce33c429cce00",
              "file": "recce_client.spec.js",
              "line": 1263,
              "column": 3
            },
            {
              "title": "request_client_login: real email writes audit + mail + invite; unknown silent; rate-limit silent; admin_list_invites by user",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1519,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 73,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1281:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 1281
                        },
                        "snippet": "  1279 |       }),\n  1280 |     })).json();\n> 1281 |     expect(seed.ok).toBe(true);\n       |                     ^\n  1282 |     const cuId = seed.client_user_id;\n  1283 |\n  1284 |     const baseAudit = await (await ctx.post(`${DB}/rpc/admin_list_audit`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 1281
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1279 |       }),\n  1280 |     })).json();\n> 1281 |     expect(seed.ok).toBe(true);\n       |                     ^\n  1282 |     const cuId = seed.client_user_id;\n  1283 |\n  1284 |     const baseAudit = await (await ctx.post(`${DB}/rpc/admin_list_audit`, { headers: HC,\n    at /var/www/360lm/tests/recce_client.spec.js:1281:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:27.517Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-18-—--c6d2b--admin-list-invites-by-user-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 1281
                      }
                    },
                    {
                      "workerIndex": 1520,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 82,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1281:21",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 21,
                          "line": 1281
                        },
                        "snippet": "  1279 |       }),\n  1280 |     })).json();\n> 1281 |     expect(seed.ok).toBe(true);\n       |                     ^\n  1282 |     const cuId = seed.client_user_id;\n  1283 |\n  1284 |     const baseAudit = await (await ctx.post(`${DB}/rpc/admin_list_audit`, { headers: HC,"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 21,
                            "line": 1281
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1279 |       }),\n  1280 |     })).json();\n> 1281 |     expect(seed.ok).toBe(true);\n       |                     ^\n  1282 |     const cuId = seed.client_user_id;\n  1283 |\n  1284 |     const baseAudit = await (await ctx.post(`${DB}/rpc/admin_list_audit`, { headers: HC,\n    at /var/www/360lm/tests/recce_client.spec.js:1281:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:29:28.455Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-4-18-—--c6d2b--admin-list-invites-by-user-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 21,
                        "line": 1281
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-f8e1821767b44229c080",
              "file": "recce_client.spec.js",
              "line": 1263,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 7.0 — get_branding_items RPC",
          "file": "recce_client.spec.js",
          "line": 1402,
          "column": 6,
          "specs": [
            {
              "title": "returns brandings + empty approvals for a fresh session; forbidden for wrong user",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 514,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 93,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1432:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 1432
                        },
                        "snippet": "  1430 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1431 |     });\n> 1432 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1433 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n  1434 |     expect(ses.ok).toBe(true);\n  1435 |     const sessionId = ses.session_id;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 1432
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1430 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1431 |     });\n> 1432 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1433 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n  1434 |     expect(ses.ok).toBe(true);\n  1435 |     const sessionId = ses.session_id;\n    at /var/www/360lm/tests/recce_client.spec.js:1432:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:34.530Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-0-—-g-73a58-on-forbidden-for-wrong-user-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 1432
                      }
                    },
                    {
                      "workerIndex": 515,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 94,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1432:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 1432
                        },
                        "snippet": "  1430 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1431 |     });\n> 1432 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1433 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n  1434 |     expect(ses.ok).toBe(true);\n  1435 |     const sessionId = ses.session_id;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 1432
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1430 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1431 |     });\n> 1432 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1433 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n  1434 |     expect(ses.ok).toBe(true);\n  1435 |     const sessionId = ses.session_id;\n    at /var/www/360lm/tests/recce_client.spec.js:1432:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:29:35.421Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-0-—-g-73a58-on-forbidden-for-wrong-user-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 1432
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-353203c22c1f4c75b683",
              "file": "recce_client.spec.js",
              "line": 1403,
              "column": 3
            },
            {
              "title": "returns brandings + empty approvals for a fresh session; forbidden for wrong user",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1521,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 99,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1432:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 1432
                        },
                        "snippet": "  1430 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1431 |     });\n> 1432 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1433 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n  1434 |     expect(ses.ok).toBe(true);\n  1435 |     const sessionId = ses.session_id;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 1432
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1430 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1431 |     });\n> 1432 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1433 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n  1434 |     expect(ses.ok).toBe(true);\n  1435 |     const sessionId = ses.session_id;\n    at /var/www/360lm/tests/recce_client.spec.js:1432:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:29.534Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-0-—-g-73a58-on-forbidden-for-wrong-user-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 1432
                      }
                    },
                    {
                      "workerIndex": 1522,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 93,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1432:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 1432
                        },
                        "snippet": "  1430 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1431 |     });\n> 1432 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1433 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n  1434 |     expect(ses.ok).toBe(true);\n  1435 |     const sessionId = ses.session_id;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 1432
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1430 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1431 |     });\n> 1432 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1433 |     const ses = await rpc(ctx, 'consume_invite', { p_invite_id: inv.invite_id });\n  1434 |     expect(ses.ok).toBe(true);\n  1435 |     const sessionId = ses.session_id;\n    at /var/www/360lm/tests/recce_client.spec.js:1432:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:29:30.519Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-0-—-g-73a58-on-forbidden-for-wrong-user-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 1432
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-4bf3b77c422b072eaae9",
              "file": "recce_client.spec.js",
              "line": 1403,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 7.0 — client_set_branding_response RPC",
          "file": "recce_client.spec.js",
          "line": 1481,
          "column": 6,
          "specs": [
            {
              "title": "upserts branding_approvals, derives overall status, blocks on bad session/forbidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 516,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 233,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1526:25",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 25,
                          "line": 1526
                        },
                        "snippet": "  1524 |         p_session_id: sessionId, p_sub_id: 'sub-not-real-7', p_items: [],\n  1525 |       });\n> 1526 |       expect(badSub.ok).toBe(false);\n       |                         ^\n  1527 |       expect(badSub.error).toBe('forbidden');\n  1528 |\n  1529 |       // 3. happy path — 2 items, one approved, one with changes"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 25,
                            "line": 1526
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1524 |         p_session_id: sessionId, p_sub_id: 'sub-not-real-7', p_items: [],\n  1525 |       });\n> 1526 |       expect(badSub.ok).toBe(false);\n       |                         ^\n  1527 |       expect(badSub.error).toBe('forbidden');\n  1528 |\n  1529 |       // 3. happy path — 2 items, one approved, one with changes\n    at /var/www/360lm/tests/recce_client.spec.js:1526:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:36.317Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-0-—-c-ad06b-ks-on-bad-session-forbidden-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 25,
                        "line": 1526
                      }
                    },
                    {
                      "workerIndex": 517,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 221,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1526:25",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 25,
                          "line": 1526
                        },
                        "snippet": "  1524 |         p_session_id: sessionId, p_sub_id: 'sub-not-real-7', p_items: [],\n  1525 |       });\n> 1526 |       expect(badSub.ok).toBe(false);\n       |                         ^\n  1527 |       expect(badSub.error).toBe('forbidden');\n  1528 |\n  1529 |       // 3. happy path — 2 items, one approved, one with changes"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 25,
                            "line": 1526
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1524 |         p_session_id: sessionId, p_sub_id: 'sub-not-real-7', p_items: [],\n  1525 |       });\n> 1526 |       expect(badSub.ok).toBe(false);\n       |                         ^\n  1527 |       expect(badSub.error).toBe('forbidden');\n  1528 |\n  1529 |       // 3. happy path — 2 items, one approved, one with changes\n    at /var/www/360lm/tests/recce_client.spec.js:1526:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:29:37.397Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-0-—-c-ad06b-ks-on-bad-session-forbidden-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 25,
                        "line": 1526
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-fbaf8b64b48dfabf6ac2",
              "file": "recce_client.spec.js",
              "line": 1482,
              "column": 3
            },
            {
              "title": "upserts branding_approvals, derives overall status, blocks on bad session/forbidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1523,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 189,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1526:25",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 25,
                          "line": 1526
                        },
                        "snippet": "  1524 |         p_session_id: sessionId, p_sub_id: 'sub-not-real-7', p_items: [],\n  1525 |       });\n> 1526 |       expect(badSub.ok).toBe(false);\n       |                         ^\n  1527 |       expect(badSub.error).toBe('forbidden');\n  1528 |\n  1529 |       // 3. happy path — 2 items, one approved, one with changes"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 25,
                            "line": 1526
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1524 |         p_session_id: sessionId, p_sub_id: 'sub-not-real-7', p_items: [],\n  1525 |       });\n> 1526 |       expect(badSub.ok).toBe(false);\n       |                         ^\n  1527 |       expect(badSub.error).toBe('forbidden');\n  1528 |\n  1529 |       // 3. happy path — 2 items, one approved, one with changes\n    at /var/www/360lm/tests/recce_client.spec.js:1526:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:31.545Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-0-—-c-ad06b-ks-on-bad-session-forbidden-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 25,
                        "line": 1526
                      }
                    },
                    {
                      "workerIndex": 1524,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 166,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1526:25",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 25,
                          "line": 1526
                        },
                        "snippet": "  1524 |         p_session_id: sessionId, p_sub_id: 'sub-not-real-7', p_items: [],\n  1525 |       });\n> 1526 |       expect(badSub.ok).toBe(false);\n       |                         ^\n  1527 |       expect(badSub.error).toBe('forbidden');\n  1528 |\n  1529 |       // 3. happy path — 2 items, one approved, one with changes"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 25,
                            "line": 1526
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mfalse\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1524 |         p_session_id: sessionId, p_sub_id: 'sub-not-real-7', p_items: [],\n  1525 |       });\n> 1526 |       expect(badSub.ok).toBe(false);\n       |                         ^\n  1527 |       expect(badSub.error).toBe('forbidden');\n  1528 |\n  1529 |       // 3. happy path — 2 items, one approved, one with changes\n    at /var/www/360lm/tests/recce_client.spec.js:1526:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:29:32.549Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-0-—-c-ad06b-ks-on-bad-session-forbidden-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 25,
                        "line": 1526
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-700b62e2f9eb5c56fb03",
              "file": "recce_client.spec.js",
              "line": 1482,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 7 — PWA respondable page (E2E)",
          "file": "recce_client.spec.js",
          "line": 1589,
          "column": 6,
          "specs": [
            {
              "title": "magic-link → viewer → Review Items → fill decisions → submit → DB verified",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 518,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 288,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1625:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 1625
                        },
                        "snippet": "  1623 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1624 |     });\n> 1625 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1626 |     const cuId = inv.client_user_id;\n  1627 |\n  1628 |     try {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 1625
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1623 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1624 |     });\n> 1625 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1626 |     const cuId = inv.client_user_id;\n  1627 |\n  1628 |     try {\n    at /var/www/360lm/tests/recce_client.spec.js:1625:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:38.381Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-—-PWA-53590-ions-→-submit-→-DB-verified-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-—-PWA-53590-ions-→-submit-→-DB-verified-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 1625
                      }
                    },
                    {
                      "workerIndex": 519,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 298,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1625:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 1625
                        },
                        "snippet": "  1623 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1624 |     });\n> 1625 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1626 |     const cuId = inv.client_user_id;\n  1627 |\n  1628 |     try {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 1625
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1623 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1624 |     });\n> 1625 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1626 |     const cuId = inv.client_user_id;\n  1627 |\n  1628 |     try {\n    at /var/www/360lm/tests/recce_client.spec.js:1625:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:29:39.665Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-—-PWA-53590-ions-→-submit-→-DB-verified-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-—-PWA-53590-ions-→-submit-→-DB-verified-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 1625
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-cb66da40a42b2f37dcb8",
              "file": "recce_client.spec.js",
              "line": 1596,
              "column": 3
            },
            {
              "title": "photo proxy serves brandings_0.jpg via HMAC token (signed by Hub session)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 520,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 312,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:40.957Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-7cec539bc3376d6e6196",
              "file": "recce_client.spec.js",
              "line": 1718,
              "column": 3
            },
            {
              "title": "magic-link → viewer → Review Items → fill decisions → submit → DB verified",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1525,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 294,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1625:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 1625
                        },
                        "snippet": "  1623 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1624 |     });\n> 1625 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1626 |     const cuId = inv.client_user_id;\n  1627 |\n  1628 |     try {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 1625
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1623 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1624 |     });\n> 1625 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1626 |     const cuId = inv.client_user_id;\n  1627 |\n  1628 |     try {\n    at /var/www/360lm/tests/recce_client.spec.js:1625:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:33.473Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-—-PWA-53590-ions-→-submit-→-DB-verified-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-—-PWA-53590-ions-→-submit-→-DB-verified-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 1625
                      }
                    },
                    {
                      "workerIndex": 1526,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 307,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/recce_client.spec.js:1625:20",
                        "location": {
                          "file": "/var/www/360lm/tests/recce_client.spec.js",
                          "column": 20,
                          "line": 1625
                        },
                        "snippet": "  1623 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1624 |     });\n> 1625 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1626 |     const cuId = inv.client_user_id;\n  1627 |\n  1628 |     try {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce_client.spec.js",
                            "column": 20,
                            "line": 1625
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mundefined\u001b[39m\n\n  1623 |       p_display_name: `PW7 ${s}`, p_invited_by: 'harish',\n  1624 |     });\n> 1625 |     expect(inv.ok).toBe(true);\n       |                    ^\n  1626 |     const cuId = inv.client_user_id;\n  1627 |\n  1628 |     try {\n    at /var/www/360lm/tests/recce_client.spec.js:1625:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:29:34.737Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-—-PWA-53590-ions-→-submit-→-DB-verified-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce_client-Phase-7-—-PWA-53590-ions-→-submit-→-DB-verified-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce_client.spec.js",
                        "column": 20,
                        "line": 1625
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-141e08bd03b03ec91579",
              "file": "recce_client.spec.js",
              "line": 1596,
              "column": 3
            },
            {
              "title": "photo proxy serves brandings_0.jpg via HMAC token (signed by Hub session)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1527,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 114,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:36.061Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "6b4c999b8a1aeac1a890-54f655edeae7575a85b5",
              "file": "recce_client.spec.js",
              "line": 1718,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "recce.spec.js",
      "file": "recce.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Branding Recce — Page Load",
          "file": "recce.spec.js",
          "line": 45,
          "column": 6,
          "specs": [
            {
              "title": "no session — page either restricts or loads without crash",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 520,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 266,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:41.364Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-1753c18a0885d52c9a8b",
              "file": "recce.spec.js",
              "line": 46,
              "column": 3
            },
            {
              "title": "with hub session — home view renders",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 520,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8157,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:60:43",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 43,
                          "line": 60
                        },
                        "snippet": "  58 |     const homeView = page.locator('#view-home');\n  59 |     const restricted = page.locator('text=Access Restricted');\n> 60 |     await expect(homeView.or(restricted)).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  61 |   });\n  62 | });\n  63 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 43,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n\n\n  58 |     const homeView = page.locator('#view-home');\n  59 |     const restricted = page.locator('text=Access Restricted');\n> 60 |     await expect(homeView.or(restricted)).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  61 |   });\n  62 | });\n  63 |\n    at /var/www/360lm/tests/recce.spec.js:60:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:29:41.744Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Pag-80f9f-session-—-home-view-renders-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Pag-80f9f-session-—-home-view-renders-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 43,
                        "line": 60
                      }
                    },
                    {
                      "workerIndex": 521,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8232,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:60:43",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 43,
                          "line": 60
                        },
                        "snippet": "  58 |     const homeView = page.locator('#view-home');\n  59 |     const restricted = page.locator('text=Access Restricted');\n> 60 |     await expect(homeView.or(restricted)).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  61 |   });\n  62 | });\n  63 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 43,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n\n\n  58 |     const homeView = page.locator('#view-home');\n  59 |     const restricted = page.locator('text=Access Restricted');\n> 60 |     await expect(homeView.or(restricted)).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  61 |   });\n  62 | });\n  63 |\n    at /var/www/360lm/tests/recce.spec.js:60:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:29:50.797Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Pag-80f9f-session-—-home-view-renders-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Pag-80f9f-session-—-home-view-renders-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 43,
                        "line": 60
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-4e2ec7a3c80b44ada57c",
              "file": "recce.spec.js",
              "line": 54,
              "column": 3
            },
            {
              "title": "no session — page either restricts or loads without crash",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1527,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 277,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:36.259Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-a7868269ce4d683dca11",
              "file": "recce.spec.js",
              "line": 46,
              "column": 3
            },
            {
              "title": "with hub session — home view renders",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1527,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8155,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:60:43",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 43,
                          "line": 60
                        },
                        "snippet": "  58 |     const homeView = page.locator('#view-home');\n  59 |     const restricted = page.locator('text=Access Restricted');\n> 60 |     await expect(homeView.or(restricted)).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  61 |   });\n  62 | });\n  63 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 43,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n\n\n  58 |     const homeView = page.locator('#view-home');\n  59 |     const restricted = page.locator('text=Access Restricted');\n> 60 |     await expect(homeView.or(restricted)).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  61 |   });\n  62 | });\n  63 |\n    at /var/www/360lm/tests/recce.spec.js:60:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:36.649Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Pag-80f9f-session-—-home-view-renders-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Pag-80f9f-session-—-home-view-renders-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 43,
                        "line": 60
                      }
                    },
                    {
                      "workerIndex": 1528,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8220,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:60:43",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 43,
                          "line": 60
                        },
                        "snippet": "  58 |     const homeView = page.locator('#view-home');\n  59 |     const restricted = page.locator('text=Access Restricted');\n> 60 |     await expect(homeView.or(restricted)).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  61 |   });\n  62 | });\n  63 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 43,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#view-home').or(locator('text=Access Restricted'))\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#view-home').or(locator('text=Access Restricted'))\u001b[22m\n\n\n  58 |     const homeView = page.locator('#view-home');\n  59 |     const restricted = page.locator('text=Access Restricted');\n> 60 |     await expect(homeView.or(restricted)).toBeVisible({ timeout: 8000 });\n     |                                           ^\n  61 |   });\n  62 | });\n  63 |\n    at /var/www/360lm/tests/recce.spec.js:60:43"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:29:45.618Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Pag-80f9f-session-—-home-view-renders-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Pag-80f9f-session-—-home-view-renders-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 43,
                        "line": 60
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-266a5a3329e21b0c0eb9",
              "file": "recce.spec.js",
              "line": 54,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — Hero Section",
          "file": "recce.spec.js",
          "line": 64,
          "column": 6,
          "specs": [
            {
              "title": "hero Hub button exists and navigates to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 72,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 522,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 376,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:30:00.076Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 72,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d0678eca61955c559273",
              "file": "recce.spec.js",
              "line": 69,
              "column": 3
            },
            {
              "title": "hero shows today-date populated (init complete)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 522,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30704,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 51,
                            "line": 82
                          },
                          "message": "Error: locator.textContent: Target page, context or browser has been closed\n\n  80 |     await page.goto(BASE_URL);\n  81 |     await waitForRecceInit(page);\n> 82 |     const txt = await page.locator('#today-date').textContent();\n     |                                                   ^\n  83 |     expect(txt).not.toBe('—');\n  84 |     expect(txt.length).toBeGreaterThan(3);\n  85 |   });\n    at /var/www/360lm/tests/recce.spec.js:82:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:30:00.688Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Her-fa580-te-populated-init-complete--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Her-fa580-te-populated-init-complete--android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 523,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30178,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 51,
                            "line": 82
                          },
                          "message": "Error: locator.textContent: Target page, context or browser has been closed\n\n  80 |     await page.goto(BASE_URL);\n  81 |     await waitForRecceInit(page);\n> 82 |     const txt = await page.locator('#today-date').textContent();\n     |                                                   ^\n  83 |     expect(txt).not.toBe('—');\n  84 |     expect(txt.length).toBeGreaterThan(3);\n  85 |   });\n    at /var/www/360lm/tests/recce.spec.js:82:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:30:33.896Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Her-fa580-te-populated-init-complete--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Her-fa580-te-populated-init-complete--android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-ce30c36ecd7a8bce1b47",
              "file": "recce.spec.js",
              "line": 79,
              "column": 3
            },
            {
              "title": "hero Hub button exists and navigates to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 72,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1529,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 284,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:54.848Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 72,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-28ba7b0f3b4e7160480d",
              "file": "recce.spec.js",
              "line": 69,
              "column": 3
            },
            {
              "title": "hero shows today-date populated (init complete)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1529,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30133,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 51,
                            "line": 82
                          },
                          "message": "Error: locator.textContent: Target page, context or browser has been closed\n\n  80 |     await page.goto(BASE_URL);\n  81 |     await waitForRecceInit(page);\n> 82 |     const txt = await page.locator('#today-date').textContent();\n     |                                                   ^\n  83 |     expect(txt).not.toBe('—');\n  84 |     expect(txt.length).toBeGreaterThan(3);\n  85 |   });\n    at /var/www/360lm/tests/recce.spec.js:82:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:29:55.290Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Her-fa580-te-populated-init-complete--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Her-fa580-te-populated-init-complete--desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1530,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30174,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 51,
                            "line": 82
                          },
                          "message": "Error: locator.textContent: Target page, context or browser has been closed\n\n  80 |     await page.goto(BASE_URL);\n  81 |     await waitForRecceInit(page);\n> 82 |     const txt = await page.locator('#today-date').textContent();\n     |                                                   ^\n  83 |     expect(txt).not.toBe('—');\n  84 |     expect(txt.length).toBeGreaterThan(3);\n  85 |   });\n    at /var/www/360lm/tests/recce.spec.js:82:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:30:26.503Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Her-fa580-te-populated-init-complete--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Her-fa580-te-populated-init-complete--desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-b2b4a5b858c6f28097a1",
              "file": "recce.spec.js",
              "line": 79,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — Settings Screen",
          "file": "recce.spec.js",
          "line": 88,
          "column": 6,
          "specs": [
            {
              "title": "settings screen opens via gear icon",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 97,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 524,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30226,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:31:05.632Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 97,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-5fce5--screen-opens-via-gear-icon-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-5fce5--screen-opens-via-gear-icon-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 525,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30121,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:31:37.059Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 97,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-5fce5--screen-opens-via-gear-icon-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-5fce5--screen-opens-via-gear-icon-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-7cfdb05507781d19e4c1",
              "file": "recce.spec.js",
              "line": 93,
              "column": 3
            },
            {
              "title": "settings hdr has Hub button (injected by injectHubButtons)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 106,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 526,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30119,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:32:08.274Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 106,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-33cbc-jected-by-injectHubButtons--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-33cbc-jected-by-injectHubButtons--android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 527,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30143,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:32:39.523Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 106,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-33cbc-jected-by-injectHubButtons--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-33cbc-jected-by-injectHubButtons--android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-43d147e7ce8304f4d248",
              "file": "recce.spec.js",
              "line": 102,
              "column": 3
            },
            {
              "title": "settings Hub button navigates to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 116,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 528,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30175,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:33:10.617Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 116,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-d338a-ub-button-navigates-to-hub--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-d338a-ub-button-navigates-to-hub--android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 529,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30120,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:33:41.916Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 116,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-d338a-ub-button-navigates-to-hub--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-d338a-ub-button-navigates-to-hub--android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-bb4a3763afdab1fd351c",
              "file": "recce.spec.js",
              "line": 112,
              "column": 3
            },
            {
              "title": "settings screen opens via gear icon",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 97,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1531,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30166,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:30:57.854Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 97,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-5fce5--screen-opens-via-gear-icon-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-5fce5--screen-opens-via-gear-icon-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1532,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30158,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:31:29.155Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 97,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-5fce5--screen-opens-via-gear-icon-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-5fce5--screen-opens-via-gear-icon-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-44ed18ecb42e594c0b85",
              "file": "recce.spec.js",
              "line": 93,
              "column": 3
            },
            {
              "title": "settings hdr has Hub button (injected by injectHubButtons)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 106,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1533,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30132,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:32:00.534Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 106,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-33cbc-jected-by-injectHubButtons--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-33cbc-jected-by-injectHubButtons--desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1534,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30323,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:32:31.776Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 106,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-33cbc-jected-by-injectHubButtons--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-33cbc-jected-by-injectHubButtons--desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-a591eb06fe80a692ed9e",
              "file": "recce.spec.js",
              "line": 102,
              "column": 3
            },
            {
              "title": "settings Hub button navigates to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 116,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1535,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30150,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:33:03.686Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 116,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-d338a-ub-button-navigates-to-hub--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-d338a-ub-button-navigates-to-hub--desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1536,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30130,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:33:35.223Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 116,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-d338a-ub-button-navigates-to-hub--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-Set-d338a-ub-button-navigates-to-hub--desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-3a3c6537d3e93ee9628a",
              "file": "recce.spec.js",
              "line": 112,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — Existing Functionality Unchanged",
          "file": "recce.spec.js",
          "line": 124,
          "column": 6,
          "specs": [
            {
              "title": "stats cards visible on home",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 132,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 530,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 267,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:34:13.006Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 132,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-583c8d69d0547dbfb56d",
              "file": "recce.spec.js",
              "line": 129,
              "column": 3
            },
            {
              "title": "New Recce button exists in DOM (hidden for admin users, visible for field agents)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 139,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 530,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 179,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:34:13.437Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 139,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-ec42f7c5c6dd7545fd67",
              "file": "recce.spec.js",
              "line": 136,
              "column": 3
            },
            {
              "title": "language toggle button visible in hero",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 147,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 530,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 178,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:34:13.652Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 147,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d82c8374086879bd627f",
              "file": "recce.spec.js",
              "line": 144,
              "column": 3
            },
            {
              "title": "existing Hub button in hero still works",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 154,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 530,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 172,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:34:13.855Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 154,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-4a8ea58eef21687bc344",
              "file": "recce.spec.js",
              "line": 151,
              "column": 3
            },
            {
              "title": "stats cards visible on home",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 132,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1537,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 296,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:34:06.624Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 132,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-b8d172515e7502575bed",
              "file": "recce.spec.js",
              "line": 129,
              "column": 3
            },
            {
              "title": "New Recce button exists in DOM (hidden for admin users, visible for field agents)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 139,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1537,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 231,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:34:07.119Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 139,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-286cffb21bd655860a5c",
              "file": "recce.spec.js",
              "line": 136,
              "column": 3
            },
            {
              "title": "language toggle button visible in hero",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 147,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1537,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 214,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:34:07.390Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 147,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d422b9473e63f3982a2e",
              "file": "recce.spec.js",
              "line": 144,
              "column": 3
            },
            {
              "title": "existing Hub button in hero still works",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 154,
                        "column": 29
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1537,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 201,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:34:07.627Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 154,
                            "column": 29
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-192ed33ae9f0ae180ff9",
              "file": "recce.spec.js",
              "line": 151,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v9 Delete Modal (B2/B3/B4 — I1/I3)",
          "file": "recce.spec.js",
          "line": 171,
          "column": 6,
          "specs": [
            {
              "title": "I1: delete toggle button shows \"View Deleted\" (not \"🗑 Deleted\")",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 178,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 530,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10142,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:34:14.051Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 178,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-ffb47deb7915088dce02",
              "file": "recce.spec.js",
              "line": 176,
              "column": 3
            },
            {
              "title": "I1: toggle label flips to \"Hide Deleted\" when clicked",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 187,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 530,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10143,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:34:24.209Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 187,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-7ffcdd277c5c49ea70b0",
              "file": "recce.spec.js",
              "line": 185,
              "column": 3
            },
            {
              "title": "delete modal opens on 🗑 button click",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 197,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 530,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10197,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:34:34.373Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 197,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-74b7d917d5e58853863e",
              "file": "recce.spec.js",
              "line": 195,
              "column": 3
            },
            {
              "title": "B2: confirm button has no-reason class (grey) on modal open",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 207,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 530,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10150,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:34:44.597Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 207,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d164c8cfa7c4ae7dc12d",
              "file": "recce.spec.js",
              "line": 205,
              "column": 3
            },
            {
              "title": "B2: no-reason class removed when reason is selected",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 220,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 530,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10182,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:34:54.763Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 220,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-f28ceb5b4817a96370cd",
              "file": "recce.spec.js",
              "line": 218,
              "column": 3
            },
            {
              "title": "B3: toast fires when Delete clicked without selecting a reason",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 232,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 530,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10199,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:35:04.972Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 232,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-8d432e3c43ec933e624f",
              "file": "recce.spec.js",
              "line": 230,
              "column": 3
            },
            {
              "title": "B4: delete modal scrolls on mobile (max-height + overflow-y:auto)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 530,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30060,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 66,
                            "line": 245
                          },
                          "message": "Error: locator.evaluate: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#del-modal-bg .del-modal')\u001b[22m\n\n\n  243 |   test('B4: delete modal scrolls on mobile (max-height + overflow-y:auto)', async ({ page }) => {\n  244 |     await page.goto(BASE_URL);\n> 245 |     const overY = await page.locator('#del-modal-bg .del-modal').evaluate(\n      |                                                                  ^\n  246 |       el => getComputedStyle(el).overflowY\n  247 |     );\n  248 |     expect(overY).toBe('auto');\n    at /var/www/360lm/tests/recce.spec.js:245:66"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:35:15.186Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--49eb0-max-height-overflow-y-auto--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--49eb0-max-height-overflow-y-auto--android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 531,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30104,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 66,
                            "line": 245
                          },
                          "message": "Error: locator.evaluate: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#del-modal-bg .del-modal')\u001b[22m\n\n\n  243 |   test('B4: delete modal scrolls on mobile (max-height + overflow-y:auto)', async ({ page }) => {\n  244 |     await page.goto(BASE_URL);\n> 245 |     const overY = await page.locator('#del-modal-bg .del-modal').evaluate(\n      |                                                                  ^\n  246 |       el => getComputedStyle(el).overflowY\n  247 |     );\n  248 |     expect(overY).toBe('auto');\n    at /var/www/360lm/tests/recce.spec.js:245:66"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:35:46.050Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--49eb0-max-height-overflow-y-auto--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--49eb0-max-height-overflow-y-auto--android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d074fded181ba795f702",
              "file": "recce.spec.js",
              "line": 243,
              "column": 3
            },
            {
              "title": "I3: Test Recce reason option has danger class",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 532,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5247,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:257:48",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 48,
                          "line": 257
                        },
                        "snippet": "  255 |   test('I3: Test Recce reason option has danger class', async ({ page }) => {\n  256 |     await page.goto(BASE_URL);\n> 257 |     await expect(page.locator('#reason-test')).toHaveClass(/danger/);\n      |                                                ^\n  258 |   });\n  259 |\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 48,
                            "line": 257
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n\n\n  255 |   test('I3: Test Recce reason option has danger class', async ({ page }) => {\n  256 |     await page.goto(BASE_URL);\n> 257 |     await expect(page.locator('#reason-test')).toHaveClass(/danger/);\n      |                                                ^\n  258 |   });\n  259 |\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:257:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:36:17.112Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--e9cca-son-option-has-danger-class-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--e9cca-son-option-has-danger-class-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 48,
                        "line": 257
                      }
                    },
                    {
                      "workerIndex": 533,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5239,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:257:48",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 48,
                          "line": 257
                        },
                        "snippet": "  255 |   test('I3: Test Recce reason option has danger class', async ({ page }) => {\n  256 |     await page.goto(BASE_URL);\n> 257 |     await expect(page.locator('#reason-test')).toHaveClass(/danger/);\n      |                                                ^\n  258 |   });\n  259 |\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 48,
                            "line": 257
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n\n\n  255 |   test('I3: Test Recce reason option has danger class', async ({ page }) => {\n  256 |     await page.goto(BASE_URL);\n> 257 |     await expect(page.locator('#reason-test')).toHaveClass(/danger/);\n      |                                                ^\n  258 |   });\n  259 |\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:257:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:36:23.312Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--e9cca-son-option-has-danger-class-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--e9cca-son-option-has-danger-class-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 48,
                        "line": 257
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-343e94f579d36f7a757e",
              "file": "recce.spec.js",
              "line": 255,
              "column": 3
            },
            {
              "title": "I3: Test Recce option shows ⚠️ icon",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 534,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5219,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:262:53",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 53,
                          "line": 262
                        },
                        "snippet": "  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n  261 |     await page.goto(BASE_URL);\n> 262 |     await expect(page.locator('#reason-test .ico')).toContainText('⚠️');\n      |                                                     ^\n  263 |   });\n  264 |\n  265 |   test('cancel button closes delete modal cleanly', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 53,
                            "line": 262
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n\n\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n  261 |     await page.goto(BASE_URL);\n> 262 |     await expect(page.locator('#reason-test .ico')).toContainText('⚠️');\n      |                                                     ^\n  263 |   });\n  264 |\n  265 |   test('cancel button closes delete modal cleanly', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:262:53"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:36:29.534Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--0a0c5--Recce-option-shows-⚠️-icon-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--0a0c5--Recce-option-shows-⚠️-icon-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 53,
                        "line": 262
                      }
                    },
                    {
                      "workerIndex": 535,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5214,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:262:53",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 53,
                          "line": 262
                        },
                        "snippet": "  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n  261 |     await page.goto(BASE_URL);\n> 262 |     await expect(page.locator('#reason-test .ico')).toContainText('⚠️');\n      |                                                     ^\n  263 |   });\n  264 |\n  265 |   test('cancel button closes delete modal cleanly', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 53,
                            "line": 262
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n\n\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n  261 |     await page.goto(BASE_URL);\n> 262 |     await expect(page.locator('#reason-test .ico')).toContainText('⚠️');\n      |                                                     ^\n  263 |   });\n  264 |\n  265 |   test('cancel button closes delete modal cleanly', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:262:53"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:36:35.705Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--0a0c5--Recce-option-shows-⚠️-icon-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--0a0c5--Recce-option-shows-⚠️-icon-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 53,
                        "line": 262
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-fe3ed23b6e3d4d7bf390",
              "file": "recce.spec.js",
              "line": 260,
              "column": 3
            },
            {
              "title": "cancel button closes delete modal cleanly",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 267,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 536,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10263,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:36:41.924Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 267,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-596c740377755ffd23af",
              "file": "recce.spec.js",
              "line": 265,
              "column": 3
            },
            {
              "title": "I1: delete toggle button shows \"View Deleted\" (not \"🗑 Deleted\")",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 178,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1537,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10141,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:34:07.849Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 178,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-adbba25ffb365b2f9f89",
              "file": "recce.spec.js",
              "line": 176,
              "column": 3
            },
            {
              "title": "I1: toggle label flips to \"Hide Deleted\" when clicked",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 187,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1537,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10152,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:34:18.011Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 187,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-b832889f85d6527d6aab",
              "file": "recce.spec.js",
              "line": 185,
              "column": 3
            },
            {
              "title": "delete modal opens on 🗑 button click",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 197,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1537,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10180,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:34:28.184Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 197,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-566aaf8dada24ba0adec",
              "file": "recce.spec.js",
              "line": 195,
              "column": 3
            },
            {
              "title": "B2: confirm button has no-reason class (grey) on modal open",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 207,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1537,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10164,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:34:38.385Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 207,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-02c0045840bc7dd39db5",
              "file": "recce.spec.js",
              "line": 205,
              "column": 3
            },
            {
              "title": "B2: no-reason class removed when reason is selected",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 220,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1537,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10169,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:34:48.566Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 220,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-68089c0e18f7fec862dc",
              "file": "recce.spec.js",
              "line": 218,
              "column": 3
            },
            {
              "title": "B3: toast fires when Delete clicked without selecting a reason",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 232,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1537,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10192,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:34:58.753Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 232,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d97019103c7b86a9f55c",
              "file": "recce.spec.js",
              "line": 230,
              "column": 3
            },
            {
              "title": "B4: delete modal scrolls on mobile (max-height + overflow-y:auto)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1537,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30070,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 66,
                            "line": 245
                          },
                          "message": "Error: locator.evaluate: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#del-modal-bg .del-modal')\u001b[22m\n\n\n  243 |   test('B4: delete modal scrolls on mobile (max-height + overflow-y:auto)', async ({ page }) => {\n  244 |     await page.goto(BASE_URL);\n> 245 |     const overY = await page.locator('#del-modal-bg .del-modal').evaluate(\n      |                                                                  ^\n  246 |       el => getComputedStyle(el).overflowY\n  247 |     );\n  248 |     expect(overY).toBe('auto');\n    at /var/www/360lm/tests/recce.spec.js:245:66"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:35:08.972Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--49eb0-max-height-overflow-y-auto--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--49eb0-max-height-overflow-y-auto--desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1538,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30086,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 66,
                            "line": 245
                          },
                          "message": "Error: locator.evaluate: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#del-modal-bg .del-modal')\u001b[22m\n\n\n  243 |   test('B4: delete modal scrolls on mobile (max-height + overflow-y:auto)', async ({ page }) => {\n  244 |     await page.goto(BASE_URL);\n> 245 |     const overY = await page.locator('#del-modal-bg .del-modal').evaluate(\n      |                                                                  ^\n  246 |       el => getComputedStyle(el).overflowY\n  247 |     );\n  248 |     expect(overY).toBe('auto');\n    at /var/www/360lm/tests/recce.spec.js:245:66"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:35:39.863Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--49eb0-max-height-overflow-y-auto--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--49eb0-max-height-overflow-y-auto--desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-975a81d9fcb49f76697e",
              "file": "recce.spec.js",
              "line": 243,
              "column": 3
            },
            {
              "title": "I3: Test Recce reason option has danger class",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1539,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5231,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:257:48",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 48,
                          "line": 257
                        },
                        "snippet": "  255 |   test('I3: Test Recce reason option has danger class', async ({ page }) => {\n  256 |     await page.goto(BASE_URL);\n> 257 |     await expect(page.locator('#reason-test')).toHaveClass(/danger/);\n      |                                                ^\n  258 |   });\n  259 |\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 48,
                            "line": 257
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n\n\n  255 |   test('I3: Test Recce reason option has danger class', async ({ page }) => {\n  256 |     await page.goto(BASE_URL);\n> 257 |     await expect(page.locator('#reason-test')).toHaveClass(/danger/);\n      |                                                ^\n  258 |   });\n  259 |\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:257:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:36:11.061Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--e9cca-son-option-has-danger-class-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--e9cca-son-option-has-danger-class-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 48,
                        "line": 257
                      }
                    },
                    {
                      "workerIndex": 1540,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5248,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:257:48",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 48,
                          "line": 257
                        },
                        "snippet": "  255 |   test('I3: Test Recce reason option has danger class', async ({ page }) => {\n  256 |     await page.goto(BASE_URL);\n> 257 |     await expect(page.locator('#reason-test')).toHaveClass(/danger/);\n      |                                                ^\n  258 |   });\n  259 |\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 48,
                            "line": 257
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test')\nExpected pattern: \u001b[32m/danger/\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test')\u001b[22m\n\n\n  255 |   test('I3: Test Recce reason option has danger class', async ({ page }) => {\n  256 |     await page.goto(BASE_URL);\n> 257 |     await expect(page.locator('#reason-test')).toHaveClass(/danger/);\n      |                                                ^\n  258 |   });\n  259 |\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:257:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:36:17.297Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--e9cca-son-option-has-danger-class-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--e9cca-son-option-has-danger-class-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 48,
                        "line": 257
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-2c22af5cd25674047383",
              "file": "recce.spec.js",
              "line": 255,
              "column": 3
            },
            {
              "title": "I3: Test Recce option shows ⚠️ icon",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1541,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5278,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:262:53",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 53,
                          "line": 262
                        },
                        "snippet": "  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n  261 |     await page.goto(BASE_URL);\n> 262 |     await expect(page.locator('#reason-test .ico')).toContainText('⚠️');\n      |                                                     ^\n  263 |   });\n  264 |\n  265 |   test('cancel button closes delete modal cleanly', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 53,
                            "line": 262
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n\n\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n  261 |     await page.goto(BASE_URL);\n> 262 |     await expect(page.locator('#reason-test .ico')).toContainText('⚠️');\n      |                                                     ^\n  263 |   });\n  264 |\n  265 |   test('cancel button closes delete modal cleanly', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:262:53"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:36:23.573Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--0a0c5--Recce-option-shows-⚠️-icon-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--0a0c5--Recce-option-shows-⚠️-icon-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 53,
                        "line": 262
                      }
                    },
                    {
                      "workerIndex": 1542,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:262:53",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 53,
                          "line": 262
                        },
                        "snippet": "  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n  261 |     await page.goto(BASE_URL);\n> 262 |     await expect(page.locator('#reason-test .ico')).toContainText('⚠️');\n      |                                                     ^\n  263 |   });\n  264 |\n  265 |   test('cancel button closes delete modal cleanly', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 53,
                            "line": 262
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reason-test .ico')\nExpected substring: \u001b[32m\"⚠️\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reason-test .ico')\u001b[22m\n\n\n  260 |   test('I3: Test Recce option shows ⚠️ icon', async ({ page }) => {\n  261 |     await page.goto(BASE_URL);\n> 262 |     await expect(page.locator('#reason-test .ico')).toContainText('⚠️');\n      |                                                     ^\n  263 |   });\n  264 |\n  265 |   test('cancel button closes delete modal cleanly', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:262:53"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:36:29.987Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--0a0c5--Recce-option-shows-⚠️-icon-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v9--0a0c5--Recce-option-shows-⚠️-icon-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 53,
                        "line": 262
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-2626ce0fbf43e023a673",
              "file": "recce.spec.js",
              "line": 260,
              "column": 3
            },
            {
              "title": "cancel button closes delete modal cleanly",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 267,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1543,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10251,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:36:36.215Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 267,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-a0f042117f9f304f199a",
              "file": "recce.spec.js",
              "line": 265,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v10 Inside View max 5 photos",
          "file": "recce.spec.js",
          "line": 318,
          "column": 6,
          "specs": [
            {
              "title": "inside view label shows \"(optional, max 5)\"",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 536,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 187,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:323:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 323
                        },
                        "snippet": "  321 |     const src = await resp.text();\n  322 |     // Verify the i18n key for inside view now maps to max 5 (not max 3)\n> 323 |     expect(src).toContain('form_opt_max5');\n      |                 ^\n  324 |     expect(src).toContain('(optional, max 5)');\n  325 |     expect(src).not.toContain('form_opt_max3\">(optional, max 3)'); // inside should NOT have max 3\n  326 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 323
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  321 |     const src = await resp.text();\n  322 |     // Verify the i18n key for inside view now maps to max 5 (not max 3)\n> 323 |     expect(src).toContain('form_opt_max5');\n      |                 ^\n  324 |     expect(src).toContain('(optional, max 5)');\n  325 |     expect(src).not.toContain('form_opt_max3\">(optional, max 3)'); // inside should NOT have max 3\n  326 |   });\n    at /var/www/360lm/tests/recce.spec.js:323:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:36:52.375Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d4414-label-shows-optional-max-5--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d4414-label-shows-optional-max-5--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 323
                      }
                    },
                    {
                      "workerIndex": 537,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 235,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:323:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 323
                        },
                        "snippet": "  321 |     const src = await resp.text();\n  322 |     // Verify the i18n key for inside view now maps to max 5 (not max 3)\n> 323 |     expect(src).toContain('form_opt_max5');\n      |                 ^\n  324 |     expect(src).toContain('(optional, max 5)');\n  325 |     expect(src).not.toContain('form_opt_max3\">(optional, max 3)'); // inside should NOT have max 3\n  326 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 323
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  321 |     const src = await resp.text();\n  322 |     // Verify the i18n key for inside view now maps to max 5 (not max 3)\n> 323 |     expect(src).toContain('form_opt_max5');\n      |                 ^\n  324 |     expect(src).toContain('(optional, max 5)');\n  325 |     expect(src).not.toContain('form_opt_max3\">(optional, max 3)'); // inside should NOT have max 3\n  326 |   });\n    at /var/www/360lm/tests/recce.spec.js:323:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:36:53.415Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d4414-label-shows-optional-max-5--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d4414-label-shows-optional-max-5--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 323
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-e5a037bf3a572788f8a5",
              "file": "recce.spec.js",
              "line": 319,
              "column": 3
            },
            {
              "title": "front view label still shows \"(min 1, max 3)\"",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 538,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 239,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:331:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 331
                        },
                        "snippet": "  329 |     const resp = await page.request.get(BASE_URL);\n  330 |     const src = await resp.text();\n> 331 |     expect(src).toContain('form_min1_max3');\n      |                 ^\n  332 |     expect(src).toContain('(min 1, max 3)');\n  333 |   });\n  334 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 331
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  329 |     const resp = await page.request.get(BASE_URL);\n  330 |     const src = await resp.text();\n> 331 |     expect(src).toContain('form_min1_max3');\n      |                 ^\n  332 |     expect(src).toContain('(min 1, max 3)');\n  333 |   });\n  334 |\n    at /var/www/360lm/tests/recce.spec.js:331:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:36:54.603Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e80d-el-still-shows-min-1-max-3--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e80d-el-still-shows-min-1-max-3--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 331
                      }
                    },
                    {
                      "workerIndex": 539,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 258,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:331:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 331
                        },
                        "snippet": "  329 |     const resp = await page.request.get(BASE_URL);\n  330 |     const src = await resp.text();\n> 331 |     expect(src).toContain('form_min1_max3');\n      |                 ^\n  332 |     expect(src).toContain('(min 1, max 3)');\n  333 |   });\n  334 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 331
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  329 |     const resp = await page.request.get(BASE_URL);\n  330 |     const src = await resp.text();\n> 331 |     expect(src).toContain('form_min1_max3');\n      |                 ^\n  332 |     expect(src).toContain('(min 1, max 3)');\n  333 |   });\n  334 |\n    at /var/www/360lm/tests/recce.spec.js:331:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:36:55.821Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e80d-el-still-shows-min-1-max-3--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e80d-el-still-shows-min-1-max-3--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 331
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-2ae6a84e5315a3352ec1",
              "file": "recce.spec.js",
              "line": 328,
              "column": 3
            },
            {
              "title": "add-inside button hidden after 3 photos (before limit) — regression guard",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 540,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 309,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:347:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 347
                        },
                        "snippet": "  345 |     const src = await page.content();\n  346 |     // Source must contain the updated limit logic\n> 347 |     expect(src).toContain(\"type==='inside'?5:3\");\n      |                 ^\n  348 |   });\n  349 |\n  350 |   test('SW version is current (recce-v18 or later)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 347
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m\n\n  345 |     const src = await page.content();\n  346 |     // Source must contain the updated limit logic\n> 347 |     expect(src).toContain(\"type==='inside'?5:3\");\n      |                 ^\n  348 |   });\n  349 |\n  350 |   test('SW version is current (recce-v18 or later)', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:347:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:36:57.075Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d20fb-re-limit-—-regression-guard-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d20fb-re-limit-—-regression-guard-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 347
                      }
                    },
                    {
                      "workerIndex": 541,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 299,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:347:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 347
                        },
                        "snippet": "  345 |     const src = await page.content();\n  346 |     // Source must contain the updated limit logic\n> 347 |     expect(src).toContain(\"type==='inside'?5:3\");\n      |                 ^\n  348 |   });\n  349 |\n  350 |   test('SW version is current (recce-v18 or later)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 347
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m\n\n  345 |     const src = await page.content();\n  346 |     // Source must contain the updated limit logic\n> 347 |     expect(src).toContain(\"type==='inside'?5:3\");\n      |                 ^\n  348 |   });\n  349 |\n  350 |   test('SW version is current (recce-v18 or later)', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:347:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:36:58.351Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d20fb-re-limit-—-regression-guard-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d20fb-re-limit-—-regression-guard-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 347
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-57db3c51e2e91116dd5a",
              "file": "recce.spec.js",
              "line": 335,
              "column": 3
            },
            {
              "title": "SW version is current (recce-v18 or later)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 542,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 289,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:353:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 353
                        },
                        "snippet": "  351 |     const resp = await page.request.get(BASE_URL);\n  352 |     const src = await resp.text();\n> 353 |     expect(src).toMatch(/recce-v\\d+/);\n      |                 ^\n  354 |   });\n  355 | });\n  356 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 353
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  351 |     const resp = await page.request.get(BASE_URL);\n  352 |     const src = await resp.text();\n> 353 |     expect(src).toMatch(/recce-v\\d+/);\n      |                 ^\n  354 |   });\n  355 | });\n  356 |\n    at /var/www/360lm/tests/recce.spec.js:353:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:36:59.709Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-7a5b1-current-recce-v18-or-later--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-7a5b1-current-recce-v18-or-later--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 353
                      }
                    },
                    {
                      "workerIndex": 543,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 613,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:353:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 353
                        },
                        "snippet": "  351 |     const resp = await page.request.get(BASE_URL);\n  352 |     const src = await resp.text();\n> 353 |     expect(src).toMatch(/recce-v\\d+/);\n      |                 ^\n  354 |   });\n  355 | });\n  356 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 353
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  351 |     const resp = await page.request.get(BASE_URL);\n  352 |     const src = await resp.text();\n> 353 |     expect(src).toMatch(/recce-v\\d+/);\n      |                 ^\n  354 |   });\n  355 | });\n  356 |\n    at /var/www/360lm/tests/recce.spec.js:353:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:37:01.218Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-7a5b1-current-recce-v18-or-later--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-7a5b1-current-recce-v18-or-later--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 353
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-1af3034387d82ec6a504",
              "file": "recce.spec.js",
              "line": 350,
              "column": 3
            },
            {
              "title": "inside view label shows \"(optional, max 5)\"",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1543,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 209,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:323:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 323
                        },
                        "snippet": "  321 |     const src = await resp.text();\n  322 |     // Verify the i18n key for inside view now maps to max 5 (not max 3)\n> 323 |     expect(src).toContain('form_opt_max5');\n      |                 ^\n  324 |     expect(src).toContain('(optional, max 5)');\n  325 |     expect(src).not.toContain('form_opt_max3\">(optional, max 3)'); // inside should NOT have max 3\n  326 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 323
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  321 |     const src = await resp.text();\n  322 |     // Verify the i18n key for inside view now maps to max 5 (not max 3)\n> 323 |     expect(src).toContain('form_opt_max5');\n      |                 ^\n  324 |     expect(src).toContain('(optional, max 5)');\n  325 |     expect(src).not.toContain('form_opt_max3\">(optional, max 3)'); // inside should NOT have max 3\n  326 |   });\n    at /var/www/360lm/tests/recce.spec.js:323:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:36:46.622Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d4414-label-shows-optional-max-5--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d4414-label-shows-optional-max-5--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 323
                      }
                    },
                    {
                      "workerIndex": 1544,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 249,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:323:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 323
                        },
                        "snippet": "  321 |     const src = await resp.text();\n  322 |     // Verify the i18n key for inside view now maps to max 5 (not max 3)\n> 323 |     expect(src).toContain('form_opt_max5');\n      |                 ^\n  324 |     expect(src).toContain('(optional, max 5)');\n  325 |     expect(src).not.toContain('form_opt_max3\">(optional, max 3)'); // inside should NOT have max 3\n  326 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 323
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_opt_max5\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  321 |     const src = await resp.text();\n  322 |     // Verify the i18n key for inside view now maps to max 5 (not max 3)\n> 323 |     expect(src).toContain('form_opt_max5');\n      |                 ^\n  324 |     expect(src).toContain('(optional, max 5)');\n  325 |     expect(src).not.toContain('form_opt_max3\">(optional, max 3)'); // inside should NOT have max 3\n  326 |   });\n    at /var/www/360lm/tests/recce.spec.js:323:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:36:47.729Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d4414-label-shows-optional-max-5--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d4414-label-shows-optional-max-5--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 323
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-290e517ef1b0aa6969f1",
              "file": "recce.spec.js",
              "line": 319,
              "column": 3
            },
            {
              "title": "front view label still shows \"(min 1, max 3)\"",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1545,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 261,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:331:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 331
                        },
                        "snippet": "  329 |     const resp = await page.request.get(BASE_URL);\n  330 |     const src = await resp.text();\n> 331 |     expect(src).toContain('form_min1_max3');\n      |                 ^\n  332 |     expect(src).toContain('(min 1, max 3)');\n  333 |   });\n  334 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 331
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  329 |     const resp = await page.request.get(BASE_URL);\n  330 |     const src = await resp.text();\n> 331 |     expect(src).toContain('form_min1_max3');\n      |                 ^\n  332 |     expect(src).toContain('(min 1, max 3)');\n  333 |   });\n  334 |\n    at /var/www/360lm/tests/recce.spec.js:331:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:36:49.029Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e80d-el-still-shows-min-1-max-3--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e80d-el-still-shows-min-1-max-3--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 331
                      }
                    },
                    {
                      "workerIndex": 1546,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 277,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:331:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 331
                        },
                        "snippet": "  329 |     const resp = await page.request.get(BASE_URL);\n  330 |     const src = await resp.text();\n> 331 |     expect(src).toContain('form_min1_max3');\n      |                 ^\n  332 |     expect(src).toContain('(min 1, max 3)');\n  333 |   });\n  334 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 331
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"form_min1_max3\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  329 |     const resp = await page.request.get(BASE_URL);\n  330 |     const src = await resp.text();\n> 331 |     expect(src).toContain('form_min1_max3');\n      |                 ^\n  332 |     expect(src).toContain('(min 1, max 3)');\n  333 |   });\n  334 |\n    at /var/www/360lm/tests/recce.spec.js:331:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:36:50.320Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e80d-el-still-shows-min-1-max-3--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-5e80d-el-still-shows-min-1-max-3--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 331
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-c2186030fc7e5640d768",
              "file": "recce.spec.js",
              "line": 328,
              "column": 3
            },
            {
              "title": "add-inside button hidden after 3 photos (before limit) — regression guard",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1547,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 386,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:347:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 347
                        },
                        "snippet": "  345 |     const src = await page.content();\n  346 |     // Source must contain the updated limit logic\n> 347 |     expect(src).toContain(\"type==='inside'?5:3\");\n      |                 ^\n  348 |   });\n  349 |\n  350 |   test('SW version is current (recce-v18 or later)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 347
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m\n\n  345 |     const src = await page.content();\n  346 |     // Source must contain the updated limit logic\n> 347 |     expect(src).toContain(\"type==='inside'?5:3\");\n      |                 ^\n  348 |   });\n  349 |\n  350 |   test('SW version is current (recce-v18 or later)', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:347:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:36:51.729Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d20fb-re-limit-—-regression-guard-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d20fb-re-limit-—-regression-guard-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 347
                      }
                    },
                    {
                      "workerIndex": 1548,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 253,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:347:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 347
                        },
                        "snippet": "  345 |     const src = await page.content();\n  346 |     // Source must contain the updated limit logic\n> 347 |     expect(src).toContain(\"type==='inside'?5:3\");\n      |                 ^\n  348 |   });\n  349 |\n  350 |   test('SW version is current (recce-v18 or later)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 347
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"type==='inside'?5:3\"\u001b[39m\nReceived string:    \u001b[31m\"<html><head><meta name=\\\"color-scheme\\\" content=\\\"light dark\\\"></head><body><pre style=\\\"word-wrap: break-word; white-space: pre-wrap;\\\">404 page not found\u001b[39m\n\u001b[31m</pre></body></html>\"\u001b[39m\n\n  345 |     const src = await page.content();\n  346 |     // Source must contain the updated limit logic\n> 347 |     expect(src).toContain(\"type==='inside'?5:3\");\n      |                 ^\n  348 |   });\n  349 |\n  350 |   test('SW version is current (recce-v18 or later)', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:347:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:36:53.083Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d20fb-re-limit-—-regression-guard-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-d20fb-re-limit-—-regression-guard-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 347
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-61a619f43cbfc0e44e4f",
              "file": "recce.spec.js",
              "line": 335,
              "column": 3
            },
            {
              "title": "SW version is current (recce-v18 or later)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1549,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 251,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:353:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 353
                        },
                        "snippet": "  351 |     const resp = await page.request.get(BASE_URL);\n  352 |     const src = await resp.text();\n> 353 |     expect(src).toMatch(/recce-v\\d+/);\n      |                 ^\n  354 |   });\n  355 | });\n  356 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 353
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  351 |     const resp = await page.request.get(BASE_URL);\n  352 |     const src = await resp.text();\n> 353 |     expect(src).toMatch(/recce-v\\d+/);\n      |                 ^\n  354 |   });\n  355 | });\n  356 |\n    at /var/www/360lm/tests/recce.spec.js:353:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:36:54.346Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-7a5b1-current-recce-v18-or-later--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-7a5b1-current-recce-v18-or-later--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 353
                      }
                    },
                    {
                      "workerIndex": 1550,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 261,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:353:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 353
                        },
                        "snippet": "  351 |     const resp = await page.request.get(BASE_URL);\n  352 |     const src = await resp.text();\n> 353 |     expect(src).toMatch(/recce-v\\d+/);\n      |                 ^\n  354 |   });\n  355 | });\n  356 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 353
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  351 |     const resp = await page.request.get(BASE_URL);\n  352 |     const src = await resp.text();\n> 353 |     expect(src).toMatch(/recce-v\\d+/);\n      |                 ^\n  354 |   });\n  355 | });\n  356 |\n    at /var/www/360lm/tests/recce.spec.js:353:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:36:55.610Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-7a5b1-current-recce-v18-or-later--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v10-7a5b1-current-recce-v18-or-later--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 353
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-b80925bd12c4d97ff081",
              "file": "recce.spec.js",
              "line": 350,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v11 Restore Deleted Recce",
          "file": "recce.spec.js",
          "line": 358,
          "column": 6,
          "specs": [
            {
              "title": "restore-btn present on deleted cards for admin",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 365,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 544,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10300,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:03.475Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 365,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-6463bebd75c84f09c335",
              "file": "recce.spec.js",
              "line": 363,
              "column": 3
            },
            {
              "title": "restore-btn has green outline style (not red)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 380,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 544,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10180,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:13.980Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 380,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-48a07d43216b73bf9ac2",
              "file": "recce.spec.js",
              "line": 378,
              "column": 3
            },
            {
              "title": "restore source contains restoreRecce function",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 544,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 165,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:393:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 393
                        },
                        "snippet": "  391 |     const resp = await page.request.get(BASE_URL);\n  392 |     const src = await resp.text();\n> 393 |     expect(src).toContain('restoreRecce');\n      |                 ^\n  394 |     expect(src).toContain('deleted_at:');\n  395 |   });\n  396 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 393
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  391 |     const resp = await page.request.get(BASE_URL);\n  392 |     const src = await resp.text();\n> 393 |     expect(src).toContain('restoreRecce');\n      |                 ^\n  394 |     expect(src).toContain('deleted_at:');\n  395 |   });\n  396 | });\n    at /var/www/360lm/tests/recce.spec.js:393:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:24.184Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v11-574ea-tains-restoreRecce-function-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v11-574ea-tains-restoreRecce-function-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 393
                      }
                    },
                    {
                      "workerIndex": 545,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 252,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:393:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 393
                        },
                        "snippet": "  391 |     const resp = await page.request.get(BASE_URL);\n  392 |     const src = await resp.text();\n> 393 |     expect(src).toContain('restoreRecce');\n      |                 ^\n  394 |     expect(src).toContain('deleted_at:');\n  395 |   });\n  396 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 393
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  391 |     const resp = await page.request.get(BASE_URL);\n  392 |     const src = await resp.text();\n> 393 |     expect(src).toContain('restoreRecce');\n      |                 ^\n  394 |     expect(src).toContain('deleted_at:');\n  395 |   });\n  396 | });\n    at /var/www/360lm/tests/recce.spec.js:393:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:37:25.373Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v11-574ea-tains-restoreRecce-function-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v11-574ea-tains-restoreRecce-function-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 393
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d74f1b14ccdf5c810915",
              "file": "recce.spec.js",
              "line": 390,
              "column": 3
            },
            {
              "title": "restore-btn present on deleted cards for admin",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 365,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1551,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10249,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:36:56.902Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 365,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-e40fa3249ad2fc9f04a6",
              "file": "recce.spec.js",
              "line": 363,
              "column": 3
            },
            {
              "title": "restore-btn has green outline style (not red)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 380,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1551,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10183,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:07.338Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 380,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-490192bede51abdc7aba",
              "file": "recce.spec.js",
              "line": 378,
              "column": 3
            },
            {
              "title": "restore source contains restoreRecce function",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1551,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 226,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:393:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 393
                        },
                        "snippet": "  391 |     const resp = await page.request.get(BASE_URL);\n  392 |     const src = await resp.text();\n> 393 |     expect(src).toContain('restoreRecce');\n      |                 ^\n  394 |     expect(src).toContain('deleted_at:');\n  395 |   });\n  396 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 393
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  391 |     const resp = await page.request.get(BASE_URL);\n  392 |     const src = await resp.text();\n> 393 |     expect(src).toContain('restoreRecce');\n      |                 ^\n  394 |     expect(src).toContain('deleted_at:');\n  395 |   });\n  396 | });\n    at /var/www/360lm/tests/recce.spec.js:393:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:17.544Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v11-574ea-tains-restoreRecce-function-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v11-574ea-tains-restoreRecce-function-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 393
                      }
                    },
                    {
                      "workerIndex": 1552,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 281,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:393:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 393
                        },
                        "snippet": "  391 |     const resp = await page.request.get(BASE_URL);\n  392 |     const src = await resp.text();\n> 393 |     expect(src).toContain('restoreRecce');\n      |                 ^\n  394 |     expect(src).toContain('deleted_at:');\n  395 |   });\n  396 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 393
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"restoreRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  391 |     const resp = await page.request.get(BASE_URL);\n  392 |     const src = await resp.text();\n> 393 |     expect(src).toContain('restoreRecce');\n      |                 ^\n  394 |     expect(src).toContain('deleted_at:');\n  395 |   });\n  396 | });\n    at /var/www/360lm/tests/recce.spec.js:393:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:37:18.705Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v11-574ea-tains-restoreRecce-function-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v11-574ea-tains-restoreRecce-function-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 393
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-26f3d4e4c96bc271855f",
              "file": "recce.spec.js",
              "line": 390,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v9 Sync Text (B5)",
          "file": "recce.spec.js",
          "line": 399,
          "column": 6,
          "specs": [
            {
              "title": "B5: last-sync-txt shows \"Loaded HH:MM\" after admin fetch",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 406,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10239,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:26.583Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 406,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-5d8091a756965e8306bc",
              "file": "recce.spec.js",
              "line": 404,
              "column": 3
            },
            {
              "title": "B5: last-sync-txt shows \"Loaded HH:MM\" after admin fetch",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 406,
                        "column": 21
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10275,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:20.076Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 406,
                            "column": 21
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-bcbabcaa47cb3989fcce",
              "file": "recce.spec.js",
              "line": 404,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v12 Supervisor Landing",
          "file": "recce.spec.js",
          "line": 414,
          "column": 6,
          "specs": [
            {
              "title": "supervisor sees 2 tiles on home",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 189,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:36.997Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-32dd12189820e577cc28",
              "file": "recce.spec.js",
              "line": 420,
              "column": 3
            },
            {
              "title": "first tile shows View All Recces label",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 127,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:37.231Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-76b71c62781d8f598e1b",
              "file": "recce.spec.js",
              "line": 427,
              "column": 3
            },
            {
              "title": "second tile shows Create Recce label",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 132,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:37.386Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-a225d403ad501b14b4c1",
              "file": "recce.spec.js",
              "line": 435,
              "column": 3
            },
            {
              "title": "new-btn is hidden on supervisor landing",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 108,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:37.553Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-7f134ebb5d9b57ad146f",
              "file": "recce.spec.js",
              "line": 443,
              "column": 3
            },
            {
              "title": "View All Recces tile opens admin list with back button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 109,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:37.682Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-e54999ec840db1c59e8e",
              "file": "recce.spec.js",
              "line": 451,
              "column": 3
            },
            {
              "title": "source contains renderSupLanding and _isSupervisor",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 103,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:37.811Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-98438642e8d641f2a6c3",
              "file": "recce.spec.js",
              "line": 465,
              "column": 3
            },
            {
              "title": "supervisor sees 2 tiles on home",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 220,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:30.540Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-66b7a0ac658e6bcb2278",
              "file": "recce.spec.js",
              "line": 420,
              "column": 3
            },
            {
              "title": "first tile shows View All Recces label",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 179,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:30.792Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-09892f9b1425f01edfcd",
              "file": "recce.spec.js",
              "line": 427,
              "column": 3
            },
            {
              "title": "second tile shows Create Recce label",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 117,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:31.010Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-442562ce9f25ea586725",
              "file": "recce.spec.js",
              "line": 435,
              "column": 3
            },
            {
              "title": "new-btn is hidden on supervisor landing",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 133,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:31.143Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-5649d411bb93d21e8200",
              "file": "recce.spec.js",
              "line": 443,
              "column": 3
            },
            {
              "title": "View All Recces tile opens admin list with back button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 114,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:31.302Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-b0d82362c6dae755428a",
              "file": "recce.spec.js",
              "line": 451,
              "column": 3
            },
            {
              "title": "source contains renderSupLanding and _isSupervisor",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 416,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 120,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:31.436Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 416,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-241d52c1c2b7615b4413",
              "file": "recce.spec.js",
              "line": 465,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v13 Supervisor Landing Bug Fixes",
          "file": "recce.spec.js",
          "line": 475,
          "column": 6,
          "specs": [
            {
              "title": "filter bar hidden on supervisor landing (STEP1-BUG-01)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 477,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 115,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:37.933Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 477,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-621c573624d7b4830de2",
              "file": "recce.spec.js",
              "line": 481,
              "column": 3
            },
            {
              "title": "filter bar reappears when View All Recces tapped again",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 477,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 106,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:38.069Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 477,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-17234def6ac6ea19ef0c",
              "file": "recce.spec.js",
              "line": 497,
              "column": 3
            },
            {
              "title": "source contains checkDraft call in exitSaveAndLeave (STEP3-BUG-01)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 477,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 113,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:38.198Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 477,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-e7554596fa1c08be9fe1",
              "file": "recce.spec.js",
              "line": 510,
              "column": 3
            },
            {
              "title": "filter bar hidden on supervisor landing (STEP1-BUG-01)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 477,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 112,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:31.572Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 477,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-5a3debfe5e2edf742e2a",
              "file": "recce.spec.js",
              "line": 481,
              "column": 3
            },
            {
              "title": "filter bar reappears when View All Recces tapped again",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 477,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 124,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:31.702Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 477,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-925c12b205b25c5229a9",
              "file": "recce.spec.js",
              "line": 497,
              "column": 3
            },
            {
              "title": "source contains checkDraft call in exitSaveAndLeave (STEP3-BUG-01)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 477,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 113,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:31.839Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 477,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-5db89deff03f0e8f9479",
              "file": "recce.spec.js",
              "line": 510,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — Logout clears both session keys",
          "file": "recce.spec.js",
          "line": 523,
          "column": 6,
          "specs": [
            {
              "title": "after logout lm360-session is removed",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "btn-logout removed in redesign",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 527,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 100,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:38.328Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "btn-logout removed in redesign",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 527,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-b70f817f31b4f076a866",
              "file": "recce.spec.js",
              "line": 539,
              "column": 3
            },
            {
              "title": "after logout 360exp-session is also removed",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "btn-logout removed in redesign",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 527,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 102,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:38.447Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "btn-logout removed in redesign",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 527,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-98b8db8009e20bf40780",
              "file": "recce.spec.js",
              "line": 550,
              "column": 3
            },
            {
              "title": "hub shows PIN login screen after logout (not auto-logged in)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "btn-logout removed in redesign",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 527,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 88,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:38.564Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "btn-logout removed in redesign",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 527,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-1376d88785c5b76b34f2",
              "file": "recce.spec.js",
              "line": 561,
              "column": 3
            },
            {
              "title": "logout — clears session keys and redirects to hub",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "btn-logout removed in redesign",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 527,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 97,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:38.666Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "btn-logout removed in redesign",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 527,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-3015aa9a25254306e2ad",
              "file": "recce.spec.js",
              "line": 574,
              "column": 3
            },
            {
              "title": "after logout lm360-session is removed",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "btn-logout removed in redesign",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 527,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 106,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:31.967Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "btn-logout removed in redesign",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 527,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-14c414fd5c58b50e6599",
              "file": "recce.spec.js",
              "line": 539,
              "column": 3
            },
            {
              "title": "after logout 360exp-session is also removed",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "btn-logout removed in redesign",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 527,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 108,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:32.089Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "btn-logout removed in redesign",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 527,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-f083ec7a412bc4c45386",
              "file": "recce.spec.js",
              "line": 550,
              "column": 3
            },
            {
              "title": "hub shows PIN login screen after logout (not auto-logged in)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "btn-logout removed in redesign",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 527,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 139,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:32.214Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "btn-logout removed in redesign",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 527,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-aa34efb0cf330169da86",
              "file": "recce.spec.js",
              "line": 561,
              "column": 3
            },
            {
              "title": "logout — clears session keys and redirects to hub",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "btn-logout removed in redesign",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 527,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 111,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:32.379Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "btn-logout removed in redesign",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 527,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-7474bc85a3582ac13604",
              "file": "recce.spec.js",
              "line": 574,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v14 Create Recce Field Agent View",
          "file": "recce.spec.js",
          "line": 588,
          "column": 6,
          "specs": [
            {
              "title": "Create Recce tile shows New Recce button (not direct form)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 590,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 103,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:38.783Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 590,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-639d22b29d5715c5156a",
              "file": "recce.spec.js",
              "line": 594,
              "column": 3
            },
            {
              "title": "Create Recce tile shows Back to Menu button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 590,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 112,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:38.902Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 590,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-8b35e4667b05b9e62170",
              "file": "recce.spec.js",
              "line": 603,
              "column": 3
            },
            {
              "title": "Back to Menu on create view returns to supervisor landing",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 590,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 122,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:39.032Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 590,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-81cd562a3f5f0fb739dd",
              "file": "recce.spec.js",
              "line": 611,
              "column": 3
            },
            {
              "title": "source contains supervisorCreateView and _supCreateMode",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 590,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 112,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:39.172Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 590,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-eaea6dabebff3c0fb5cd",
              "file": "recce.spec.js",
              "line": 624,
              "column": 3
            },
            {
              "title": "Create Recce tile shows New Recce button (not direct form)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 590,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 123,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:32.504Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 590,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-8c60b15037d387364f6b",
              "file": "recce.spec.js",
              "line": 594,
              "column": 3
            },
            {
              "title": "Create Recce tile shows Back to Menu button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 590,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 108,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:32.644Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 590,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-668163584e360b5f816d",
              "file": "recce.spec.js",
              "line": 603,
              "column": 3
            },
            {
              "title": "Back to Menu on create view returns to supervisor landing",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 590,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 122,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:32.769Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 590,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-878f55edf147f3cf32b7",
              "file": "recce.spec.js",
              "line": 611,
              "column": 3
            },
            {
              "title": "source contains supervisorCreateView and _supCreateMode",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "sup-tile UI removed in redesign after v14",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 590,
                        "column": 10
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 113,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:32.908Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "sup-tile UI removed in redesign after v14",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 590,
                            "column": 10
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-16d2fc1b7e5fb8249617",
              "file": "recce.spec.js",
              "line": 624,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v15 Client Field",
          "file": "recce.spec.js",
          "line": 633,
          "column": 6,
          "specs": [
            {
              "title": "source contains client_name field and renderClientChips",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 546,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 153,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:637:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 637
                        },
                        "snippet": "  635 |     const resp = await page.request.get(BASE_URL);\n  636 |     const src = await resp.text();\n> 637 |     expect(src).toContain('renderClientChips');\n      |                 ^\n  638 |     expect(src).toContain('_selectedClient');\n  639 |     expect(src).toContain('client_name');\n  640 |     expect(src).toContain('getClientName');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 637
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  635 |     const resp = await page.request.get(BASE_URL);\n  636 |     const src = await resp.text();\n> 637 |     expect(src).toContain('renderClientChips');\n      |                 ^\n  638 |     expect(src).toContain('_selectedClient');\n  639 |     expect(src).toContain('client_name');\n  640 |     expect(src).toContain('getClientName');\n    at /var/www/360lm/tests/recce.spec.js:637:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:39.305Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-5d9ea-field-and-renderClientChips-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-5d9ea-field-and-renderClientChips-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 637
                      }
                    },
                    {
                      "workerIndex": 547,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 260,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:637:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 637
                        },
                        "snippet": "  635 |     const resp = await page.request.get(BASE_URL);\n  636 |     const src = await resp.text();\n> 637 |     expect(src).toContain('renderClientChips');\n      |                 ^\n  638 |     expect(src).toContain('_selectedClient');\n  639 |     expect(src).toContain('client_name');\n  640 |     expect(src).toContain('getClientName');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 637
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  635 |     const resp = await page.request.get(BASE_URL);\n  636 |     const src = await resp.text();\n> 637 |     expect(src).toContain('renderClientChips');\n      |                 ^\n  638 |     expect(src).toContain('_selectedClient');\n  639 |     expect(src).toContain('client_name');\n  640 |     expect(src).toContain('getClientName');\n    at /var/www/360lm/tests/recce.spec.js:637:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:37:40.254Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-5d9ea-field-and-renderClientChips-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-5d9ea-field-and-renderClientChips-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 637
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-8664dc05f6f0af92aa01",
              "file": "recce.spec.js",
              "line": 634,
              "column": 3
            },
            {
              "title": "client chip grid is present in form DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 548,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 15282,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:651:49",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 49,
                          "line": 651
                        },
                        "snippet": "  649 |     await page.goto(BASE_URL);\n  650 |     await page.waitForSelector('#view-form-store', { timeout: 10000 }).catch(() => {});\n> 651 |     await expect(page.locator('#client-chips')).toHaveCount(1);\n      |                                                 ^\n  652 |   });\n  653 |\n  654 |   test('Other chip appears and reveals text input when clicked', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 49,
                            "line": 651
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  649 |     await page.goto(BASE_URL);\n  650 |     await page.waitForSelector('#view-form-store', { timeout: 10000 }).catch(() => {});\n> 651 |     await expect(page.locator('#client-chips')).toHaveCount(1);\n      |                                                 ^\n  652 |   });\n  653 |\n  654 |   test('Other chip appears and reveals text input when clicked', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:651:49"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:37:41.494Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-9f7d2-grid-is-present-in-form-DOM-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-9f7d2-grid-is-present-in-form-DOM-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 49,
                        "line": 651
                      }
                    },
                    {
                      "workerIndex": 549,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 15288,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:651:49",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 49,
                          "line": 651
                        },
                        "snippet": "  649 |     await page.goto(BASE_URL);\n  650 |     await page.waitForSelector('#view-form-store', { timeout: 10000 }).catch(() => {});\n> 651 |     await expect(page.locator('#client-chips')).toHaveCount(1);\n      |                                                 ^\n  652 |   });\n  653 |\n  654 |   test('Other chip appears and reveals text input when clicked', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 49,
                            "line": 651
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  649 |     await page.goto(BASE_URL);\n  650 |     await page.waitForSelector('#view-form-store', { timeout: 10000 }).catch(() => {});\n> 651 |     await expect(page.locator('#client-chips')).toHaveCount(1);\n      |                                                 ^\n  652 |   });\n  653 |\n  654 |   test('Other chip appears and reveals text input when clicked', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:651:49"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:37:57.882Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-9f7d2-grid-is-present-in-form-DOM-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-9f7d2-grid-is-present-in-form-DOM-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 49,
                        "line": 651
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-1fe499ea1a43aa73afb9",
              "file": "recce.spec.js",
              "line": 643,
              "column": 3
            },
            {
              "title": "Other chip appears and reveals text input when clicked",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 550,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 21262,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:667:29",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 29,
                          "line": 667
                        },
                        "snippet": "  665 |     await page.waitForSelector('#view-form-store.active', { timeout: 6000 }).catch(() => {});\n  666 |     const otherChip = page.locator('.cl-chip-other');\n> 667 |     await expect(otherChip).toBeVisible({ timeout: 5000 });\n      |                             ^\n  668 |     await otherChip.click();\n  669 |     await expect(page.locator('#client-other-wrap')).toBeVisible({ timeout: 2000 });\n  670 |     await expect(page.locator('#f-client-other')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 29,
                            "line": 667
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n\n\n  665 |     await page.waitForSelector('#view-form-store.active', { timeout: 6000 }).catch(() => {});\n  666 |     const otherChip = page.locator('.cl-chip-other');\n> 667 |     await expect(otherChip).toBeVisible({ timeout: 5000 });\n      |                             ^\n  668 |     await otherChip.click();\n  669 |     await expect(page.locator('#client-other-wrap')).toBeVisible({ timeout: 2000 });\n  670 |     await expect(page.locator('#f-client-other')).toBeVisible();\n    at /var/www/360lm/tests/recce.spec.js:667:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:38:14.186Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-18163-als-text-input-when-clicked-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-18163-als-text-input-when-clicked-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 29,
                        "line": 667
                      }
                    },
                    {
                      "workerIndex": 551,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 21240,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:667:29",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 29,
                          "line": 667
                        },
                        "snippet": "  665 |     await page.waitForSelector('#view-form-store.active', { timeout: 6000 }).catch(() => {});\n  666 |     const otherChip = page.locator('.cl-chip-other');\n> 667 |     await expect(otherChip).toBeVisible({ timeout: 5000 });\n      |                             ^\n  668 |     await otherChip.click();\n  669 |     await expect(page.locator('#client-other-wrap')).toBeVisible({ timeout: 2000 });\n  670 |     await expect(page.locator('#f-client-other')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 29,
                            "line": 667
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n\n\n  665 |     await page.waitForSelector('#view-form-store.active', { timeout: 6000 }).catch(() => {});\n  666 |     const otherChip = page.locator('.cl-chip-other');\n> 667 |     await expect(otherChip).toBeVisible({ timeout: 5000 });\n      |                             ^\n  668 |     await otherChip.click();\n  669 |     await expect(page.locator('#client-other-wrap')).toBeVisible({ timeout: 2000 });\n  670 |     await expect(page.locator('#f-client-other')).toBeVisible();\n    at /var/www/360lm/tests/recce.spec.js:667:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:38:36.424Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-18163-als-text-input-when-clicked-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-18163-als-text-input-when-clicked-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 29,
                        "line": 667
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-0c481fa381af20546351",
              "file": "recce.spec.js",
              "line": 654,
              "column": 3
            },
            {
              "title": "Next blocked and toast shown when no client selected",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 682,
                        "column": 79
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 552,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10360,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:38:58.714Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 682,
                            "column": 79
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-5355a5f858f2ec3031e8",
              "file": "recce.spec.js",
              "line": 673,
              "column": 3
            },
            {
              "title": "admin filter bar source contains af-client dropdown",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 552,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 254,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:703:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 703
                        },
                        "snippet": "  701 |     const resp = await page.request.get(BASE_URL);\n  702 |     const src = await resp.text();\n> 703 |     expect(src).toContain('af-client');\n      |                 ^\n  704 |     expect(src).toContain('All Clients');\n  705 |   });\n  706 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 703
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  701 |     const resp = await page.request.get(BASE_URL);\n  702 |     const src = await resp.text();\n> 703 |     expect(src).toContain('af-client');\n      |                 ^\n  704 |     expect(src).toContain('All Clients');\n  705 |   });\n  706 |\n    at /var/www/360lm/tests/recce.spec.js:703:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:39:09.228Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-729f4-contains-af-client-dropdown-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-729f4-contains-af-client-dropdown-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 703
                      }
                    },
                    {
                      "workerIndex": 553,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 320,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:703:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 703
                        },
                        "snippet": "  701 |     const resp = await page.request.get(BASE_URL);\n  702 |     const src = await resp.text();\n> 703 |     expect(src).toContain('af-client');\n      |                 ^\n  704 |     expect(src).toContain('All Clients');\n  705 |   });\n  706 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 703
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  701 |     const resp = await page.request.get(BASE_URL);\n  702 |     const src = await resp.text();\n> 703 |     expect(src).toContain('af-client');\n      |                 ^\n  704 |     expect(src).toContain('All Clients');\n  705 |   });\n  706 |\n    at /var/www/360lm/tests/recce.spec.js:703:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:39:10.544Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-729f4-contains-af-client-dropdown-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-729f4-contains-af-client-dropdown-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 703
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-15afe091c5c5b8810043",
              "file": "recce.spec.js",
              "line": 699,
              "column": 3
            },
            {
              "title": "Group: Client option exists in group selector",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 554,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 297,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:710:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 710
                        },
                        "snippet": "  708 |     const resp = await page.request.get(BASE_URL);\n  709 |     const src = await resp.text();\n> 710 |     expect(src).toContain('value=\"client\"');\n      |                 ^\n  711 |     expect(src).toContain('Group: Client');\n  712 |   });\n  713 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 710
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  708 |     const resp = await page.request.get(BASE_URL);\n  709 |     const src = await resp.text();\n> 710 |     expect(src).toContain('value=\"client\"');\n      |                 ^\n  711 |     expect(src).toContain('Group: Client');\n  712 |   });\n  713 |\n    at /var/www/360lm/tests/recce.spec.js:710:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:39:11.975Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f9114-on-exists-in-group-selector-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f9114-on-exists-in-group-selector-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 710
                      }
                    },
                    {
                      "workerIndex": 555,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 265,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:710:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 710
                        },
                        "snippet": "  708 |     const resp = await page.request.get(BASE_URL);\n  709 |     const src = await resp.text();\n> 710 |     expect(src).toContain('value=\"client\"');\n      |                 ^\n  711 |     expect(src).toContain('Group: Client');\n  712 |   });\n  713 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 710
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  708 |     const resp = await page.request.get(BASE_URL);\n  709 |     const src = await resp.text();\n> 710 |     expect(src).toContain('value=\"client\"');\n      |                 ^\n  711 |     expect(src).toContain('Group: Client');\n  712 |   });\n  713 |\n    at /var/www/360lm/tests/recce.spec.js:710:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:39:13.320Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f9114-on-exists-in-group-selector-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f9114-on-exists-in-group-selector-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 710
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-96b51059abc9e575731d",
              "file": "recce.spec.js",
              "line": 707,
              "column": 3
            },
            {
              "title": "settings panel has client management section",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 556,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:717:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 717
                        },
                        "snippet": "  715 |     const resp = await page.request.get(BASE_URL);\n  716 |     const src = await resp.text();\n> 717 |     expect(src).toContain('client-mgmt-list');\n      |                 ^\n  718 |     expect(src).toContain('addClientToList');\n  719 |     expect(src).toContain('removeClientFromList');\n  720 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 717
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  715 |     const resp = await page.request.get(BASE_URL);\n  716 |     const src = await resp.text();\n> 717 |     expect(src).toContain('client-mgmt-list');\n      |                 ^\n  718 |     expect(src).toContain('addClientToList');\n  719 |     expect(src).toContain('removeClientFromList');\n  720 |   });\n    at /var/www/360lm/tests/recce.spec.js:717:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:39:14.595Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f043d-s-client-management-section-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f043d-s-client-management-section-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 717
                      }
                    },
                    {
                      "workerIndex": 557,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 258,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:717:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 717
                        },
                        "snippet": "  715 |     const resp = await page.request.get(BASE_URL);\n  716 |     const src = await resp.text();\n> 717 |     expect(src).toContain('client-mgmt-list');\n      |                 ^\n  718 |     expect(src).toContain('addClientToList');\n  719 |     expect(src).toContain('removeClientFromList');\n  720 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 717
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  715 |     const resp = await page.request.get(BASE_URL);\n  716 |     const src = await resp.text();\n> 717 |     expect(src).toContain('client-mgmt-list');\n      |                 ^\n  718 |     expect(src).toContain('addClientToList');\n  719 |     expect(src).toContain('removeClientFromList');\n  720 |   });\n    at /var/www/360lm/tests/recce.spec.js:717:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:39:15.856Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f043d-s-client-management-section-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f043d-s-client-management-section-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 717
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-ca077ad27f6361add759",
              "file": "recce.spec.js",
              "line": 714,
              "column": 3
            },
            {
              "title": "recce-cfg.json contains clients array",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 558,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 279,
                      "error": {
                        "message": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)",
                        "stack": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)\n    at /var/www/360lm/tests/recce.spec.js:724:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 724
                        },
                        "snippet": "  722 |   test('recce-cfg.json contains clients array', async ({ page }) => {\n  723 |     const resp = await page.request.get(`${BASE_URL}recce-cfg.json`);\n> 724 |     const cfg = await resp.json();\n      |                 ^\n  725 |     expect(Array.isArray(cfg.clients)).toBe(true);\n  726 |   });\n  727 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 724
                          },
                          "message": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)\n\n  722 |   test('recce-cfg.json contains clients array', async ({ page }) => {\n  723 |     const resp = await page.request.get(`${BASE_URL}recce-cfg.json`);\n> 724 |     const cfg = await resp.json();\n      |                 ^\n  725 |     expect(Array.isArray(cfg.clients)).toBe(true);\n  726 |   });\n  727 | });\n    at /var/www/360lm/tests/recce.spec.js:724:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:39:17.066Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-7c404-json-contains-clients-array-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-7c404-json-contains-clients-array-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 724
                      }
                    },
                    {
                      "workerIndex": 559,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 266,
                      "error": {
                        "message": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)",
                        "stack": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)\n    at /var/www/360lm/tests/recce.spec.js:724:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 724
                        },
                        "snippet": "  722 |   test('recce-cfg.json contains clients array', async ({ page }) => {\n  723 |     const resp = await page.request.get(`${BASE_URL}recce-cfg.json`);\n> 724 |     const cfg = await resp.json();\n      |                 ^\n  725 |     expect(Array.isArray(cfg.clients)).toBe(true);\n  726 |   });\n  727 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 724
                          },
                          "message": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)\n\n  722 |   test('recce-cfg.json contains clients array', async ({ page }) => {\n  723 |     const resp = await page.request.get(`${BASE_URL}recce-cfg.json`);\n> 724 |     const cfg = await resp.json();\n      |                 ^\n  725 |     expect(Array.isArray(cfg.clients)).toBe(true);\n  726 |   });\n  727 | });\n    at /var/www/360lm/tests/recce.spec.js:724:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:39:18.376Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-7c404-json-contains-clients-array-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-7c404-json-contains-clients-array-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 724
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-c75d823bca0e44a233a6",
              "file": "recce.spec.js",
              "line": 722,
              "column": 3
            },
            {
              "title": "source contains client_name field and renderClientChips",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1553,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 147,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:637:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 637
                        },
                        "snippet": "  635 |     const resp = await page.request.get(BASE_URL);\n  636 |     const src = await resp.text();\n> 637 |     expect(src).toContain('renderClientChips');\n      |                 ^\n  638 |     expect(src).toContain('_selectedClient');\n  639 |     expect(src).toContain('client_name');\n  640 |     expect(src).toContain('getClientName');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 637
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  635 |     const resp = await page.request.get(BASE_URL);\n  636 |     const src = await resp.text();\n> 637 |     expect(src).toContain('renderClientChips');\n      |                 ^\n  638 |     expect(src).toContain('_selectedClient');\n  639 |     expect(src).toContain('client_name');\n  640 |     expect(src).toContain('getClientName');\n    at /var/www/360lm/tests/recce.spec.js:637:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:33.035Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-5d9ea-field-and-renderClientChips-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-5d9ea-field-and-renderClientChips-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 637
                      }
                    },
                    {
                      "workerIndex": 1554,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 272,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:637:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 637
                        },
                        "snippet": "  635 |     const resp = await page.request.get(BASE_URL);\n  636 |     const src = await resp.text();\n> 637 |     expect(src).toContain('renderClientChips');\n      |                 ^\n  638 |     expect(src).toContain('_selectedClient');\n  639 |     expect(src).toContain('client_name');\n  640 |     expect(src).toContain('getClientName');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 637
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderClientChips\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  635 |     const resp = await page.request.get(BASE_URL);\n  636 |     const src = await resp.text();\n> 637 |     expect(src).toContain('renderClientChips');\n      |                 ^\n  638 |     expect(src).toContain('_selectedClient');\n  639 |     expect(src).toContain('client_name');\n  640 |     expect(src).toContain('getClientName');\n    at /var/www/360lm/tests/recce.spec.js:637:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:37:34.119Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-5d9ea-field-and-renderClientChips-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-5d9ea-field-and-renderClientChips-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 637
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-1e55a38eb3040c5f54bb",
              "file": "recce.spec.js",
              "line": 634,
              "column": 3
            },
            {
              "title": "client chip grid is present in form DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1555,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 15262,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:651:49",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 49,
                          "line": 651
                        },
                        "snippet": "  649 |     await page.goto(BASE_URL);\n  650 |     await page.waitForSelector('#view-form-store', { timeout: 10000 }).catch(() => {});\n> 651 |     await expect(page.locator('#client-chips')).toHaveCount(1);\n      |                                                 ^\n  652 |   });\n  653 |\n  654 |   test('Other chip appears and reveals text input when clicked', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 49,
                            "line": 651
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  649 |     await page.goto(BASE_URL);\n  650 |     await page.waitForSelector('#view-form-store', { timeout: 10000 }).catch(() => {});\n> 651 |     await expect(page.locator('#client-chips')).toHaveCount(1);\n      |                                                 ^\n  652 |   });\n  653 |\n  654 |   test('Other chip appears and reveals text input when clicked', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:651:49"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:37:35.415Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-9f7d2-grid-is-present-in-form-DOM-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-9f7d2-grid-is-present-in-form-DOM-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 49,
                        "line": 651
                      }
                    },
                    {
                      "workerIndex": 1556,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 15310,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:651:49",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 49,
                          "line": 651
                        },
                        "snippet": "  649 |     await page.goto(BASE_URL);\n  650 |     await page.waitForSelector('#view-form-store', { timeout: 10000 }).catch(() => {});\n> 651 |     await expect(page.locator('#client-chips')).toHaveCount(1);\n      |                                                 ^\n  652 |   });\n  653 |\n  654 |   test('Other chip appears and reveals text input when clicked', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 49,
                            "line": 651
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#client-chips')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#client-chips')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  649 |     await page.goto(BASE_URL);\n  650 |     await page.waitForSelector('#view-form-store', { timeout: 10000 }).catch(() => {});\n> 651 |     await expect(page.locator('#client-chips')).toHaveCount(1);\n      |                                                 ^\n  652 |   });\n  653 |\n  654 |   test('Other chip appears and reveals text input when clicked', async ({ page }) => {\n    at /var/www/360lm/tests/recce.spec.js:651:49"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:37:51.943Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-9f7d2-grid-is-present-in-form-DOM-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-9f7d2-grid-is-present-in-form-DOM-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 49,
                        "line": 651
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-376d9e9178e5cfe587ea",
              "file": "recce.spec.js",
              "line": 643,
              "column": 3
            },
            {
              "title": "Other chip appears and reveals text input when clicked",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1557,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 21271,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:667:29",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 29,
                          "line": 667
                        },
                        "snippet": "  665 |     await page.waitForSelector('#view-form-store.active', { timeout: 6000 }).catch(() => {});\n  666 |     const otherChip = page.locator('.cl-chip-other');\n> 667 |     await expect(otherChip).toBeVisible({ timeout: 5000 });\n      |                             ^\n  668 |     await otherChip.click();\n  669 |     await expect(page.locator('#client-other-wrap')).toBeVisible({ timeout: 2000 });\n  670 |     await expect(page.locator('#f-client-other')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 29,
                            "line": 667
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n\n\n  665 |     await page.waitForSelector('#view-form-store.active', { timeout: 6000 }).catch(() => {});\n  666 |     const otherChip = page.locator('.cl-chip-other');\n> 667 |     await expect(otherChip).toBeVisible({ timeout: 5000 });\n      |                             ^\n  668 |     await otherChip.click();\n  669 |     await expect(page.locator('#client-other-wrap')).toBeVisible({ timeout: 2000 });\n  670 |     await expect(page.locator('#f-client-other')).toBeVisible();\n    at /var/www/360lm/tests/recce.spec.js:667:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:38:08.364Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-18163-als-text-input-when-clicked-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-18163-als-text-input-when-clicked-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 29,
                        "line": 667
                      }
                    },
                    {
                      "workerIndex": 1558,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 21310,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:667:29",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 29,
                          "line": 667
                        },
                        "snippet": "  665 |     await page.waitForSelector('#view-form-store.active', { timeout: 6000 }).catch(() => {});\n  666 |     const otherChip = page.locator('.cl-chip-other');\n> 667 |     await expect(otherChip).toBeVisible({ timeout: 5000 });\n      |                             ^\n  668 |     await otherChip.click();\n  669 |     await expect(page.locator('#client-other-wrap')).toBeVisible({ timeout: 2000 });\n  670 |     await expect(page.locator('#f-client-other')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 29,
                            "line": 667
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.cl-chip-other')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.cl-chip-other')\u001b[22m\n\n\n  665 |     await page.waitForSelector('#view-form-store.active', { timeout: 6000 }).catch(() => {});\n  666 |     const otherChip = page.locator('.cl-chip-other');\n> 667 |     await expect(otherChip).toBeVisible({ timeout: 5000 });\n      |                             ^\n  668 |     await otherChip.click();\n  669 |     await expect(page.locator('#client-other-wrap')).toBeVisible({ timeout: 2000 });\n  670 |     await expect(page.locator('#f-client-other')).toBeVisible();\n    at /var/www/360lm/tests/recce.spec.js:667:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:38:30.637Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-18163-als-text-input-when-clicked-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-18163-als-text-input-when-clicked-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 29,
                        "line": 667
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-e81e760851b52970239a",
              "file": "recce.spec.js",
              "line": 654,
              "column": 3
            },
            {
              "title": "Next blocked and toast shown when no client selected",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "line": 682,
                        "column": 79
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1559,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 10537,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:38:52.900Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "line": 682,
                            "column": 79
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-87c1674be7b4292ccd02",
              "file": "recce.spec.js",
              "line": 673,
              "column": 3
            },
            {
              "title": "admin filter bar source contains af-client dropdown",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1559,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 711,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:703:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 703
                        },
                        "snippet": "  701 |     const resp = await page.request.get(BASE_URL);\n  702 |     const src = await resp.text();\n> 703 |     expect(src).toContain('af-client');\n      |                 ^\n  704 |     expect(src).toContain('All Clients');\n  705 |   });\n  706 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 703
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  701 |     const resp = await page.request.get(BASE_URL);\n  702 |     const src = await resp.text();\n> 703 |     expect(src).toContain('af-client');\n      |                 ^\n  704 |     expect(src).toContain('All Clients');\n  705 |   });\n  706 |\n    at /var/www/360lm/tests/recce.spec.js:703:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:39:03.680Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-729f4-contains-af-client-dropdown-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-729f4-contains-af-client-dropdown-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 703
                      }
                    },
                    {
                      "workerIndex": 1560,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 392,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:703:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 703
                        },
                        "snippet": "  701 |     const resp = await page.request.get(BASE_URL);\n  702 |     const src = await resp.text();\n> 703 |     expect(src).toContain('af-client');\n      |                 ^\n  704 |     expect(src).toContain('All Clients');\n  705 |   });\n  706 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 703
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"af-client\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  701 |     const resp = await page.request.get(BASE_URL);\n  702 |     const src = await resp.text();\n> 703 |     expect(src).toContain('af-client');\n      |                 ^\n  704 |     expect(src).toContain('All Clients');\n  705 |   });\n  706 |\n    at /var/www/360lm/tests/recce.spec.js:703:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:39:05.890Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-729f4-contains-af-client-dropdown-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-729f4-contains-af-client-dropdown-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 703
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-2bb858090bb9f7bd31bf",
              "file": "recce.spec.js",
              "line": 699,
              "column": 3
            },
            {
              "title": "Group: Client option exists in group selector",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1561,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 318,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:710:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 710
                        },
                        "snippet": "  708 |     const resp = await page.request.get(BASE_URL);\n  709 |     const src = await resp.text();\n> 710 |     expect(src).toContain('value=\"client\"');\n      |                 ^\n  711 |     expect(src).toContain('Group: Client');\n  712 |   });\n  713 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 710
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  708 |     const resp = await page.request.get(BASE_URL);\n  709 |     const src = await resp.text();\n> 710 |     expect(src).toContain('value=\"client\"');\n      |                 ^\n  711 |     expect(src).toContain('Group: Client');\n  712 |   });\n  713 |\n    at /var/www/360lm/tests/recce.spec.js:710:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:39:07.682Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f9114-on-exists-in-group-selector-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f9114-on-exists-in-group-selector-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 710
                      }
                    },
                    {
                      "workerIndex": 1562,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 266,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:710:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 710
                        },
                        "snippet": "  708 |     const resp = await page.request.get(BASE_URL);\n  709 |     const src = await resp.text();\n> 710 |     expect(src).toContain('value=\"client\"');\n      |                 ^\n  711 |     expect(src).toContain('Group: Client');\n  712 |   });\n  713 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 710
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"value=\\\"client\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  708 |     const resp = await page.request.get(BASE_URL);\n  709 |     const src = await resp.text();\n> 710 |     expect(src).toContain('value=\"client\"');\n      |                 ^\n  711 |     expect(src).toContain('Group: Client');\n  712 |   });\n  713 |\n    at /var/www/360lm/tests/recce.spec.js:710:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:39:09.101Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f9114-on-exists-in-group-selector-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f9114-on-exists-in-group-selector-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 710
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-76259ca18815bc0b4f5d",
              "file": "recce.spec.js",
              "line": 707,
              "column": 3
            },
            {
              "title": "settings panel has client management section",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1563,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 335,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:717:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 717
                        },
                        "snippet": "  715 |     const resp = await page.request.get(BASE_URL);\n  716 |     const src = await resp.text();\n> 717 |     expect(src).toContain('client-mgmt-list');\n      |                 ^\n  718 |     expect(src).toContain('addClientToList');\n  719 |     expect(src).toContain('removeClientFromList');\n  720 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 717
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  715 |     const resp = await page.request.get(BASE_URL);\n  716 |     const src = await resp.text();\n> 717 |     expect(src).toContain('client-mgmt-list');\n      |                 ^\n  718 |     expect(src).toContain('addClientToList');\n  719 |     expect(src).toContain('removeClientFromList');\n  720 |   });\n    at /var/www/360lm/tests/recce.spec.js:717:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:39:10.394Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f043d-s-client-management-section-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f043d-s-client-management-section-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 717
                      }
                    },
                    {
                      "workerIndex": 1564,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 292,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:717:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 717
                        },
                        "snippet": "  715 |     const resp = await page.request.get(BASE_URL);\n  716 |     const src = await resp.text();\n> 717 |     expect(src).toContain('client-mgmt-list');\n      |                 ^\n  718 |     expect(src).toContain('addClientToList');\n  719 |     expect(src).toContain('removeClientFromList');\n  720 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 717
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"client-mgmt-list\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  715 |     const resp = await page.request.get(BASE_URL);\n  716 |     const src = await resp.text();\n> 717 |     expect(src).toContain('client-mgmt-list');\n      |                 ^\n  718 |     expect(src).toContain('addClientToList');\n  719 |     expect(src).toContain('removeClientFromList');\n  720 |   });\n    at /var/www/360lm/tests/recce.spec.js:717:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:39:11.794Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f043d-s-client-management-section-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-f043d-s-client-management-section-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 717
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-f9196413e362edc15f28",
              "file": "recce.spec.js",
              "line": 714,
              "column": 3
            },
            {
              "title": "recce-cfg.json contains clients array",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1565,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 257,
                      "error": {
                        "message": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)",
                        "stack": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)\n    at /var/www/360lm/tests/recce.spec.js:724:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 724
                        },
                        "snippet": "  722 |   test('recce-cfg.json contains clients array', async ({ page }) => {\n  723 |     const resp = await page.request.get(`${BASE_URL}recce-cfg.json`);\n> 724 |     const cfg = await resp.json();\n      |                 ^\n  725 |     expect(Array.isArray(cfg.clients)).toBe(true);\n  726 |   });\n  727 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 724
                          },
                          "message": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)\n\n  722 |   test('recce-cfg.json contains clients array', async ({ page }) => {\n  723 |     const resp = await page.request.get(`${BASE_URL}recce-cfg.json`);\n> 724 |     const cfg = await resp.json();\n      |                 ^\n  725 |     expect(Array.isArray(cfg.clients)).toBe(true);\n  726 |   });\n  727 | });\n    at /var/www/360lm/tests/recce.spec.js:724:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:39:13.142Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-7c404-json-contains-clients-array-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-7c404-json-contains-clients-array-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 724
                      }
                    },
                    {
                      "workerIndex": 1566,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 244,
                      "error": {
                        "message": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)",
                        "stack": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)\n    at /var/www/360lm/tests/recce.spec.js:724:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 724
                        },
                        "snippet": "  722 |   test('recce-cfg.json contains clients array', async ({ page }) => {\n  723 |     const resp = await page.request.get(`${BASE_URL}recce-cfg.json`);\n> 724 |     const cfg = await resp.json();\n      |                 ^\n  725 |     expect(Array.isArray(cfg.clients)).toBe(true);\n  726 |   });\n  727 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 724
                          },
                          "message": "SyntaxError: Unexpected non-whitespace character after JSON at position 4 (line 1 column 5)\n\n  722 |   test('recce-cfg.json contains clients array', async ({ page }) => {\n  723 |     const resp = await page.request.get(`${BASE_URL}recce-cfg.json`);\n> 724 |     const cfg = await resp.json();\n      |                 ^\n  725 |     expect(Array.isArray(cfg.clients)).toBe(true);\n  726 |   });\n  727 | });\n    at /var/www/360lm/tests/recce.spec.js:724:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:39:14.363Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-7c404-json-contains-clients-array-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v15-7c404-json-contains-clients-array-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 724
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-019d6edb9b3f2d5d487f",
              "file": "recce.spec.js",
              "line": 722,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v16 Onboarding Tour",
          "file": "recce.spec.js",
          "line": 730,
          "column": 6,
          "specs": [
            {
              "title": "SW cache is current recce version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 560,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:735:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 735
                        },
                        "snippet": "  733 |     const resp = await ctx.get(BASE_URL);\n  734 |     const src = await resp.text();\n> 735 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  736 |   });\n  737 |\n  738 |   test('source contains campaign dropdown elements and helpers', async ({}, testInfo) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 735
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  733 |     const resp = await ctx.get(BASE_URL);\n  734 |     const src = await resp.text();\n> 735 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  736 |   });\n  737 |\n  738 |   test('source contains campaign dropdown elements and helpers', async ({}, testInfo) => {\n    at /var/www/360lm/tests/recce.spec.js:735:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:39:19.623Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-7ac21-he-is-current-recce-version-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 735
                      }
                    },
                    {
                      "workerIndex": 561,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:735:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 735
                        },
                        "snippet": "  733 |     const resp = await ctx.get(BASE_URL);\n  734 |     const src = await resp.text();\n> 735 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  736 |   });\n  737 |\n  738 |   test('source contains campaign dropdown elements and helpers', async ({}, testInfo) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 735
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  733 |     const resp = await ctx.get(BASE_URL);\n  734 |     const src = await resp.text();\n> 735 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  736 |   });\n  737 |\n  738 |   test('source contains campaign dropdown elements and helpers', async ({}, testInfo) => {\n    at /var/www/360lm/tests/recce.spec.js:735:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:39:20.548Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-7ac21-he-is-current-recce-version-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 735
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-4abcc519190b1e5b4446",
              "file": "recce.spec.js",
              "line": 731,
              "column": 3
            },
            {
              "title": "source contains campaign dropdown elements and helpers",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 562,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:742:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 742
                        },
                        "snippet": "  740 |     const resp = await ctx.get(BASE_URL);\n  741 |     const src = await resp.text();\n> 742 |     expect(src).toContain('f-campaign-id');\n      |                 ^\n  743 |     expect(src).toContain('loadCampaignsForForm');\n  744 |     expect(src).toContain('onCampaignSelect');\n  745 |     expect(src).toContain('_notifySupervisor');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 742
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  740 |     const resp = await ctx.get(BASE_URL);\n  741 |     const src = await resp.text();\n> 742 |     expect(src).toContain('f-campaign-id');\n      |                 ^\n  743 |     expect(src).toContain('loadCampaignsForForm');\n  744 |     expect(src).toContain('onCampaignSelect');\n  745 |     expect(src).toContain('_notifySupervisor');\n    at /var/www/360lm/tests/recce.spec.js:742:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:39:21.480Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-24d3f-opdown-elements-and-helpers-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 742
                      }
                    },
                    {
                      "workerIndex": 563,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:742:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 742
                        },
                        "snippet": "  740 |     const resp = await ctx.get(BASE_URL);\n  741 |     const src = await resp.text();\n> 742 |     expect(src).toContain('f-campaign-id');\n      |                 ^\n  743 |     expect(src).toContain('loadCampaignsForForm');\n  744 |     expect(src).toContain('onCampaignSelect');\n  745 |     expect(src).toContain('_notifySupervisor');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 742
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  740 |     const resp = await ctx.get(BASE_URL);\n  741 |     const src = await resp.text();\n> 742 |     expect(src).toContain('f-campaign-id');\n      |                 ^\n  743 |     expect(src).toContain('loadCampaignsForForm');\n  744 |     expect(src).toContain('onCampaignSelect');\n  745 |     expect(src).toContain('_notifySupervisor');\n    at /var/www/360lm/tests/recce.spec.js:742:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:39:22.468Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-24d3f-opdown-elements-and-helpers-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 742
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-03741a0216e9ac778cd1",
              "file": "recce.spec.js",
              "line": 738,
              "column": 3
            },
            {
              "title": "campaign dropdown exists in form-store DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 564,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 17275,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:758:50",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 50,
                          "line": 758
                        },
                        "snippet": "  756 |     await page.goto(BASE_URL);\n  757 |     await page.waitForSelector('#f-campaign-id', { timeout: 12000 }).catch(() => {});\n> 758 |     await expect(page.locator('#f-campaign-id')).toHaveCount(1);\n      |                                                  ^\n  759 |   });\n  760 |\n  761 |   test('source contains branding lock banner and override', async ({}, testInfo) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 50,
                            "line": 758
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  756 |     await page.goto(BASE_URL);\n  757 |     await page.waitForSelector('#f-campaign-id', { timeout: 12000 }).catch(() => {});\n> 758 |     await expect(page.locator('#f-campaign-id')).toHaveCount(1);\n      |                                                  ^\n  759 |   });\n  760 |\n  761 |   test('source contains branding lock banner and override', async ({}, testInfo) => {\n    at /var/www/360lm/tests/recce.spec.js:758:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:39:23.553Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4ad13-wn-exists-in-form-store-DOM-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4ad13-wn-exists-in-form-store-DOM-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 50,
                        "line": 758
                      }
                    },
                    {
                      "workerIndex": 565,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 17285,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:758:50",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 50,
                          "line": 758
                        },
                        "snippet": "  756 |     await page.goto(BASE_URL);\n  757 |     await page.waitForSelector('#f-campaign-id', { timeout: 12000 }).catch(() => {});\n> 758 |     await expect(page.locator('#f-campaign-id')).toHaveCount(1);\n      |                                                  ^\n  759 |   });\n  760 |\n  761 |   test('source contains branding lock banner and override', async ({}, testInfo) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 50,
                            "line": 758
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  756 |     await page.goto(BASE_URL);\n  757 |     await page.waitForSelector('#f-campaign-id', { timeout: 12000 }).catch(() => {});\n> 758 |     await expect(page.locator('#f-campaign-id')).toHaveCount(1);\n      |                                                  ^\n  759 |   });\n  760 |\n  761 |   test('source contains branding lock banner and override', async ({}, testInfo) => {\n    at /var/www/360lm/tests/recce.spec.js:758:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:39:41.887Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4ad13-wn-exists-in-form-store-DOM-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4ad13-wn-exists-in-form-store-DOM-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 50,
                        "line": 758
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-adf6a5ca1da845401036",
              "file": "recce.spec.js",
              "line": 750,
              "column": 3
            },
            {
              "title": "source contains branding lock banner and override",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 566,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 92,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:765:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 765
                        },
                        "snippet": "  763 |     const resp = await ctx.get(BASE_URL);\n  764 |     const src = await resp.text();\n> 765 |     expect(src).toContain('branding-lock-banner');\n      |                 ^\n  766 |     expect(src).toContain('branding-override-wrap');\n  767 |     expect(src).toContain('_brandingLocked');\n  768 |     expect(src).toContain('unlockBrandingOverride');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 765
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  763 |     const resp = await ctx.get(BASE_URL);\n  764 |     const src = await resp.text();\n> 765 |     expect(src).toContain('branding-lock-banner');\n      |                 ^\n  766 |     expect(src).toContain('branding-override-wrap');\n  767 |     expect(src).toContain('_brandingLocked');\n  768 |     expect(src).toContain('unlockBrandingOverride');\n    at /var/www/360lm/tests/recce.spec.js:765:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:00.455Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-93c46-ng-lock-banner-and-override-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 765
                      }
                    },
                    {
                      "workerIndex": 567,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 226,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:765:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 765
                        },
                        "snippet": "  763 |     const resp = await ctx.get(BASE_URL);\n  764 |     const src = await resp.text();\n> 765 |     expect(src).toContain('branding-lock-banner');\n      |                 ^\n  766 |     expect(src).toContain('branding-override-wrap');\n  767 |     expect(src).toContain('_brandingLocked');\n  768 |     expect(src).toContain('unlockBrandingOverride');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 765
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  763 |     const resp = await ctx.get(BASE_URL);\n  764 |     const src = await resp.text();\n> 765 |     expect(src).toContain('branding-lock-banner');\n      |                 ^\n  766 |     expect(src).toContain('branding-override-wrap');\n  767 |     expect(src).toContain('_brandingLocked');\n  768 |     expect(src).toContain('unlockBrandingOverride');\n    at /var/www/360lm/tests/recce.spec.js:765:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:02.428Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-93c46-ng-lock-banner-and-override-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 765
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-05ef2c3cd62af13bbfb7",
              "file": "recce.spec.js",
              "line": 761,
              "column": 3
            },
            {
              "title": "branding lock banner is in form-branding view DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 568,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5458,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:775:57",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 57,
                          "line": 775
                        },
                        "snippet": "  773 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  774 |     await page.goto(BASE_URL);\n> 775 |     await expect(page.locator('#branding-lock-banner')).toHaveCount(1);\n      |                                                         ^\n  776 |     // Hidden by default (not locked on fresh load)\n  777 |     await expect(page.locator('#branding-lock-banner')).toBeHidden();\n  778 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 57,
                            "line": 775
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  773 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  774 |     await page.goto(BASE_URL);\n> 775 |     await expect(page.locator('#branding-lock-banner')).toHaveCount(1);\n      |                                                         ^\n  776 |     // Hidden by default (not locked on fresh load)\n  777 |     await expect(page.locator('#branding-lock-banner')).toBeHidden();\n  778 |   });\n    at /var/www/360lm/tests/recce.spec.js:775:57"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:04.676Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-ac83e-s-in-form-branding-view-DOM-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-ac83e-s-in-form-branding-view-DOM-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 57,
                        "line": 775
                      }
                    },
                    {
                      "workerIndex": 569,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5247,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:775:57",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 57,
                          "line": 775
                        },
                        "snippet": "  773 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  774 |     await page.goto(BASE_URL);\n> 775 |     await expect(page.locator('#branding-lock-banner')).toHaveCount(1);\n      |                                                         ^\n  776 |     // Hidden by default (not locked on fresh load)\n  777 |     await expect(page.locator('#branding-lock-banner')).toBeHidden();\n  778 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 57,
                            "line": 775
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  773 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  774 |     await page.goto(BASE_URL);\n> 775 |     await expect(page.locator('#branding-lock-banner')).toHaveCount(1);\n      |                                                         ^\n  776 |     // Hidden by default (not locked on fresh load)\n  777 |     await expect(page.locator('#branding-lock-banner')).toBeHidden();\n  778 |   });\n    at /var/www/360lm/tests/recce.spec.js:775:57"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:11.496Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-ac83e-s-in-form-branding-view-DOM-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-ac83e-s-in-form-branding-view-DOM-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 57,
                        "line": 775
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-46abfb56d399b4f9f81b",
              "file": "recce.spec.js",
              "line": 772,
              "column": 3
            },
            {
              "title": "source contains pending job tab, link job modal, archiving",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 570,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:784:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 784
                        },
                        "snippet": "  782 |     const resp = await ctx.get(BASE_URL);\n  783 |     const src = await resp.text();\n> 784 |     expect(src).toContain('pending-job-btn');\n      |                 ^\n  785 |     expect(src).toContain('togglePendingJobView');\n  786 |     expect(src).toContain('openLinkJobModal');\n  787 |     expect(src).toContain('modal-link-job');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 784
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  782 |     const resp = await ctx.get(BASE_URL);\n  783 |     const src = await resp.text();\n> 784 |     expect(src).toContain('pending-job-btn');\n      |                 ^\n  785 |     expect(src).toContain('togglePendingJobView');\n  786 |     expect(src).toContain('openLinkJobModal');\n  787 |     expect(src).toContain('modal-link-job');\n    at /var/www/360lm/tests/recce.spec.js:784:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:17.742Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-418f8-ab-link-job-modal-archiving-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 784
                      }
                    },
                    {
                      "workerIndex": 571,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 67,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:784:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 784
                        },
                        "snippet": "  782 |     const resp = await ctx.get(BASE_URL);\n  783 |     const src = await resp.text();\n> 784 |     expect(src).toContain('pending-job-btn');\n      |                 ^\n  785 |     expect(src).toContain('togglePendingJobView');\n  786 |     expect(src).toContain('openLinkJobModal');\n  787 |     expect(src).toContain('modal-link-job');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 784
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  782 |     const resp = await ctx.get(BASE_URL);\n  783 |     const src = await resp.text();\n> 784 |     expect(src).toContain('pending-job-btn');\n      |                 ^\n  785 |     expect(src).toContain('togglePendingJobView');\n  786 |     expect(src).toContain('openLinkJobModal');\n  787 |     expect(src).toContain('modal-link-job');\n    at /var/www/360lm/tests/recce.spec.js:784:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:18.654Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-418f8-ab-link-job-modal-archiving-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 784
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-6fef522c41829ce8fb4c",
              "file": "recce.spec.js",
              "line": 780,
              "column": 3
            },
            {
              "title": "source contains client_status admin badges",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 572,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:797:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 797
                        },
                        "snippet": "  795 |     const resp = await ctx.get(BASE_URL);\n  796 |     const src = await resp.text();\n> 797 |     expect(src).toContain('clientStatusBadge');\n      |                 ^\n  798 |     expect(src).toContain('archiveBadge');\n  799 |     expect(src).toContain('canArchive');\n  800 |     expect(src).toContain('client_archived');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 797
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  795 |     const resp = await ctx.get(BASE_URL);\n  796 |     const src = await resp.text();\n> 797 |     expect(src).toContain('clientStatusBadge');\n      |                 ^\n  798 |     expect(src).toContain('archiveBadge');\n  799 |     expect(src).toContain('canArchive');\n  800 |     expect(src).toContain('client_archived');\n    at /var/www/360lm/tests/recce.spec.js:797:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:19.617Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b4e29--client-status-admin-badges-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 797
                      }
                    },
                    {
                      "workerIndex": 573,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 67,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:797:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 797
                        },
                        "snippet": "  795 |     const resp = await ctx.get(BASE_URL);\n  796 |     const src = await resp.text();\n> 797 |     expect(src).toContain('clientStatusBadge');\n      |                 ^\n  798 |     expect(src).toContain('archiveBadge');\n  799 |     expect(src).toContain('canArchive');\n  800 |     expect(src).toContain('client_archived');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 797
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  795 |     const resp = await ctx.get(BASE_URL);\n  796 |     const src = await resp.text();\n> 797 |     expect(src).toContain('clientStatusBadge');\n      |                 ^\n  798 |     expect(src).toContain('archiveBadge');\n  799 |     expect(src).toContain('canArchive');\n  800 |     expect(src).toContain('client_archived');\n    at /var/www/360lm/tests/recce.spec.js:797:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:20.556Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b4e29--client-status-admin-badges-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 797
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-cff1b77d876961ee26c8",
              "file": "recce.spec.js",
              "line": 793,
              "column": 3
            },
            {
              "title": "link-job modal exists in DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 574,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5242,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:807:51",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 51,
                          "line": 807
                        },
                        "snippet": "  805 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  806 |     await page.goto(BASE_URL);\n> 807 |     await expect(page.locator('#modal-link-job')).toHaveCount(1);\n      |                                                   ^\n  808 |     await expect(page.locator('#link-job-sel')).toHaveCount(1);\n  809 |   });\n  810 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 51,
                            "line": 807
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  805 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  806 |     await page.goto(BASE_URL);\n> 807 |     await expect(page.locator('#modal-link-job')).toHaveCount(1);\n      |                                                   ^\n  808 |     await expect(page.locator('#link-job-sel')).toHaveCount(1);\n  809 |   });\n  810 |\n    at /var/www/360lm/tests/recce.spec.js:807:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:21.563Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-2f360-ink-job-modal-exists-in-DOM-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-2f360-ink-job-modal-exists-in-DOM-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 51,
                        "line": 807
                      }
                    },
                    {
                      "workerIndex": 575,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5229,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:807:51",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 51,
                          "line": 807
                        },
                        "snippet": "  805 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  806 |     await page.goto(BASE_URL);\n> 807 |     await expect(page.locator('#modal-link-job')).toHaveCount(1);\n      |                                                   ^\n  808 |     await expect(page.locator('#link-job-sel')).toHaveCount(1);\n  809 |   });\n  810 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 51,
                            "line": 807
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  805 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  806 |     await page.goto(BASE_URL);\n> 807 |     await expect(page.locator('#modal-link-job')).toHaveCount(1);\n      |                                                   ^\n  808 |     await expect(page.locator('#link-job-sel')).toHaveCount(1);\n  809 |   });\n  810 |\n    at /var/www/360lm/tests/recce.spec.js:807:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:27.871Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-2f360-ink-job-modal-exists-in-DOM-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-2f360-ink-job-modal-exists-in-DOM-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 51,
                        "line": 807
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-ae5893b2d29bccfb4c43",
              "file": "recce.spec.js",
              "line": 804,
              "column": 3
            },
            {
              "title": "v38: refreshSharedData helper exists",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 576,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:816:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 816
                        },
                        "snippet": "  814 |     const resp = await ctx.get(BASE_URL);\n  815 |     const src = await resp.text();\n> 816 |     expect(src).toContain('async function refreshSharedData');\n      |                 ^\n  817 |     expect(src).toContain('async function refreshStoresFromUrl');\n  818 |   });\n  819 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 816
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  814 |     const resp = await ctx.get(BASE_URL);\n  815 |     const src = await resp.text();\n> 816 |     expect(src).toContain('async function refreshSharedData');\n      |                 ^\n  817 |     expect(src).toContain('async function refreshStoresFromUrl');\n  818 |   });\n  819 |\n    at /var/www/360lm/tests/recce.spec.js:816:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:34.076Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-042e4-eshSharedData-helper-exists-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 816
                      }
                    },
                    {
                      "workerIndex": 577,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:816:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 816
                        },
                        "snippet": "  814 |     const resp = await ctx.get(BASE_URL);\n  815 |     const src = await resp.text();\n> 816 |     expect(src).toContain('async function refreshSharedData');\n      |                 ^\n  817 |     expect(src).toContain('async function refreshStoresFromUrl');\n  818 |   });\n  819 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 816
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  814 |     const resp = await ctx.get(BASE_URL);\n  815 |     const src = await resp.text();\n> 816 |     expect(src).toContain('async function refreshSharedData');\n      |                 ^\n  817 |     expect(src).toContain('async function refreshStoresFromUrl');\n  818 |   });\n  819 |\n    at /var/www/360lm/tests/recce.spec.js:816:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:34.976Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-042e4-eshSharedData-helper-exists-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 816
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-ea4aaa0b8f5dcec9af30",
              "file": "recce.spec.js",
              "line": 812,
              "column": 3
            },
            {
              "title": "v38: refreshSharedData is called from startNewRecce",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 578,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:825:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 825
                        },
                        "snippet": "  823 |     const src = await resp.text();\n  824 |     // startNewRecce is now async and awaits refreshSharedData (with 2.5s race)\n> 825 |     expect(src).toContain('async function startNewRecce');\n      |                 ^\n  826 |     expect(src).toMatch(/startNewRecce[\\s\\S]*?refreshSharedData/);\n  827 |   });\n  828 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 825
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  823 |     const src = await resp.text();\n  824 |     // startNewRecce is now async and awaits refreshSharedData (with 2.5s race)\n> 825 |     expect(src).toContain('async function startNewRecce');\n      |                 ^\n  826 |     expect(src).toMatch(/startNewRecce[\\s\\S]*?refreshSharedData/);\n  827 |   });\n  828 |\n    at /var/www/360lm/tests/recce.spec.js:825:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:35.881Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-d2808-s-called-from-startNewRecce-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 825
                      }
                    },
                    {
                      "workerIndex": 579,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:825:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 825
                        },
                        "snippet": "  823 |     const src = await resp.text();\n  824 |     // startNewRecce is now async and awaits refreshSharedData (with 2.5s race)\n> 825 |     expect(src).toContain('async function startNewRecce');\n      |                 ^\n  826 |     expect(src).toMatch(/startNewRecce[\\s\\S]*?refreshSharedData/);\n  827 |   });\n  828 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 825
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  823 |     const src = await resp.text();\n  824 |     // startNewRecce is now async and awaits refreshSharedData (with 2.5s race)\n> 825 |     expect(src).toContain('async function startNewRecce');\n      |                 ^\n  826 |     expect(src).toMatch(/startNewRecce[\\s\\S]*?refreshSharedData/);\n  827 |   });\n  828 |\n    at /var/www/360lm/tests/recce.spec.js:825:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:36.814Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-d2808-s-called-from-startNewRecce-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 825
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-e762732bfb8c8c60dfa6",
              "file": "recce.spec.js",
              "line": 820,
              "column": 3
            },
            {
              "title": "v38: loadBrandsFromDb no longer has extras merge leak",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 580,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:838:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 838
                        },
                        "snippet": "  836 |     expect(src).not.toContain('brands=[...dbNames,...extras]');\n  837 |     // New canonical pattern present\n> 838 |     expect(src).toContain('single source of truth');\n      |                 ^\n  839 |     expect(src).toContain(\"brands=dbNames.length?dbNames:['PHILIPS']\");\n  840 |   });\n  841 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 838
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  836 |     expect(src).not.toContain('brands=[...dbNames,...extras]');\n  837 |     // New canonical pattern present\n> 838 |     expect(src).toContain('single source of truth');\n      |                 ^\n  839 |     expect(src).toContain(\"brands=dbNames.length?dbNames:['PHILIPS']\");\n  840 |   });\n  841 |\n    at /var/www/360lm/tests/recce.spec.js:838:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:37.777Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b9a93-onger-has-extras-merge-leak-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 838
                      }
                    },
                    {
                      "workerIndex": 581,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:838:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 838
                        },
                        "snippet": "  836 |     expect(src).not.toContain('brands=[...dbNames,...extras]');\n  837 |     // New canonical pattern present\n> 838 |     expect(src).toContain('single source of truth');\n      |                 ^\n  839 |     expect(src).toContain(\"brands=dbNames.length?dbNames:['PHILIPS']\");\n  840 |   });\n  841 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 838
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  836 |     expect(src).not.toContain('brands=[...dbNames,...extras]');\n  837 |     // New canonical pattern present\n> 838 |     expect(src).toContain('single source of truth');\n      |                 ^\n  839 |     expect(src).toContain(\"brands=dbNames.length?dbNames:['PHILIPS']\");\n  840 |   });\n  841 |\n    at /var/www/360lm/tests/recce.spec.js:838:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:38.689Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b9a93-onger-has-extras-merge-leak-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 838
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-30ce96a90b7a32aa0eba",
              "file": "recce.spec.js",
              "line": 829,
              "column": 3
            },
            {
              "title": "v38: addBrandToList verifies the DB write before claiming success",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 582,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:846:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 846
                        },
                        "snippet": "  844 |     const resp = await ctx.get(BASE_URL);\n  845 |     const src = await resp.text();\n> 846 |     expect(src).toContain('VERIFY the row actually landed');\n      |                 ^\n  847 |     expect(src).toContain('Write not visible in DB');\n  848 |     expect(src).toContain('visible to all users');\n  849 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 846
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  844 |     const resp = await ctx.get(BASE_URL);\n  845 |     const src = await resp.text();\n> 846 |     expect(src).toContain('VERIFY the row actually landed');\n      |                 ^\n  847 |     expect(src).toContain('Write not visible in DB');\n  848 |     expect(src).toContain('visible to all users');\n  849 |   });\n    at /var/www/360lm/tests/recce.spec.js:846:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:39.658Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-cce73-ite-before-claiming-success-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 846
                      }
                    },
                    {
                      "workerIndex": 583,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 65,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:846:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 846
                        },
                        "snippet": "  844 |     const resp = await ctx.get(BASE_URL);\n  845 |     const src = await resp.text();\n> 846 |     expect(src).toContain('VERIFY the row actually landed');\n      |                 ^\n  847 |     expect(src).toContain('Write not visible in DB');\n  848 |     expect(src).toContain('visible to all users');\n  849 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 846
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  844 |     const resp = await ctx.get(BASE_URL);\n  845 |     const src = await resp.text();\n> 846 |     expect(src).toContain('VERIFY the row actually landed');\n      |                 ^\n  847 |     expect(src).toContain('Write not visible in DB');\n  848 |     expect(src).toContain('visible to all users');\n  849 |   });\n    at /var/www/360lm/tests/recce.spec.js:846:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:40.558Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-cce73-ite-before-claiming-success-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 846
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-53914f16994b5f494c58",
              "file": "recce.spec.js",
              "line": 842,
              "column": 3
            },
            {
              "title": "v38: fetchServerBrands backdoor disabled",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 584,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:857:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 857
                        },
                        "snippet": "  855 |     // The function exists as a stub that shows an error toast,\n  856 |     // but the body must NOT do a local write anymore\n> 857 |     expect(src).toContain('fetchServerBrands removed');\n      |                 ^\n  858 |     expect(src).toContain('This feature has been removed');\n  859 |     expect(src).not.toContain('brands=list.map(b=>b.toString().toUpperCase())');\n  860 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 857
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  855 |     // The function exists as a stub that shows an error toast,\n  856 |     // but the body must NOT do a local write anymore\n> 857 |     expect(src).toContain('fetchServerBrands removed');\n      |                 ^\n  858 |     expect(src).toContain('This feature has been removed');\n  859 |     expect(src).not.toContain('brands=list.map(b=>b.toString().toUpperCase())');\n  860 |   });\n    at /var/www/360lm/tests/recce.spec.js:857:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:41.477Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-471a3-verBrands-backdoor-disabled-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 857
                      }
                    },
                    {
                      "workerIndex": 585,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:857:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 857
                        },
                        "snippet": "  855 |     // The function exists as a stub that shows an error toast,\n  856 |     // but the body must NOT do a local write anymore\n> 857 |     expect(src).toContain('fetchServerBrands removed');\n      |                 ^\n  858 |     expect(src).toContain('This feature has been removed');\n  859 |     expect(src).not.toContain('brands=list.map(b=>b.toString().toUpperCase())');\n  860 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 857
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  855 |     // The function exists as a stub that shows an error toast,\n  856 |     // but the body must NOT do a local write anymore\n> 857 |     expect(src).toContain('fetchServerBrands removed');\n      |                 ^\n  858 |     expect(src).toContain('This feature has been removed');\n  859 |     expect(src).not.toContain('brands=list.map(b=>b.toString().toUpperCase())');\n  860 |   });\n    at /var/www/360lm/tests/recce.spec.js:857:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:42.396Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-471a3-verBrands-backdoor-disabled-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 857
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-15a8a8f7278d9aba765a",
              "file": "recce.spec.js",
              "line": 851,
              "column": 3
            },
            {
              "title": "v38: admin filter bar has Config button for supervisors",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 586,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:866:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 866
                        },
                        "snippet": "  864 |     const resp = await ctx.get(BASE_URL);\n  865 |     const src = await resp.text();\n> 866 |     expect(src).toContain('id=\"admin-config-btn\"');\n      |                 ^\n  867 |     expect(src).toContain(\"onclick=\\\"showView('settings')\\\"\");\n  868 |     expect(src).toContain('Brand / Client / Store config');\n  869 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 866
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  864 |     const resp = await ctx.get(BASE_URL);\n  865 |     const src = await resp.text();\n> 866 |     expect(src).toContain('id=\"admin-config-btn\"');\n      |                 ^\n  867 |     expect(src).toContain(\"onclick=\\\"showView('settings')\\\"\");\n  868 |     expect(src).toContain('Brand / Client / Store config');\n  869 |   });\n    at /var/www/360lm/tests/recce.spec.js:866:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:43.271Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-8700d-nfig-button-for-supervisors-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 866
                      }
                    },
                    {
                      "workerIndex": 587,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:866:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 866
                        },
                        "snippet": "  864 |     const resp = await ctx.get(BASE_URL);\n  865 |     const src = await resp.text();\n> 866 |     expect(src).toContain('id=\"admin-config-btn\"');\n      |                 ^\n  867 |     expect(src).toContain(\"onclick=\\\"showView('settings')\\\"\");\n  868 |     expect(src).toContain('Brand / Client / Store config');\n  869 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 866
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  864 |     const resp = await ctx.get(BASE_URL);\n  865 |     const src = await resp.text();\n> 866 |     expect(src).toContain('id=\"admin-config-btn\"');\n      |                 ^\n  867 |     expect(src).toContain(\"onclick=\\\"showView('settings')\\\"\");\n  868 |     expect(src).toContain('Brand / Client / Store config');\n  869 |   });\n    at /var/www/360lm/tests/recce.spec.js:866:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:44.148Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-8700d-nfig-button-for-supervisors-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 866
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-7cdad7b7e511a860e36e",
              "file": "recce.spec.js",
              "line": 862,
              "column": 3
            },
            {
              "title": "v38: DB fetches use Cache-Control no-cache header",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 588,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:877:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 877
                        },
                        "snippet": "  875 |     // PostgREST rejects unknown query params (would treat &t= as column filter),\n  876 |     // so cache-busting is via headers only.\n> 877 |     expect(src).toContain(\"'/db/brands?select=name,logo_url&order=sort_order'\");\n      |                 ^\n  878 |     expect(src).toContain(\"'Cache-Control':'no-cache'\");\n  879 |     expect(src).toContain(\"'Pragma':'no-cache'\");\n  880 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 877
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  875 |     // PostgREST rejects unknown query params (would treat &t= as column filter),\n  876 |     // so cache-busting is via headers only.\n> 877 |     expect(src).toContain(\"'/db/brands?select=name,logo_url&order=sort_order'\");\n      |                 ^\n  878 |     expect(src).toContain(\"'Cache-Control':'no-cache'\");\n  879 |     expect(src).toContain(\"'Pragma':'no-cache'\");\n  880 |   });\n    at /var/www/360lm/tests/recce.spec.js:877:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:44.992Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-7a345-che-Control-no-cache-header-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 877
                      }
                    },
                    {
                      "workerIndex": 589,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:877:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 877
                        },
                        "snippet": "  875 |     // PostgREST rejects unknown query params (would treat &t= as column filter),\n  876 |     // so cache-busting is via headers only.\n> 877 |     expect(src).toContain(\"'/db/brands?select=name,logo_url&order=sort_order'\");\n      |                 ^\n  878 |     expect(src).toContain(\"'Cache-Control':'no-cache'\");\n  879 |     expect(src).toContain(\"'Pragma':'no-cache'\");\n  880 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 877
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  875 |     // PostgREST rejects unknown query params (would treat &t= as column filter),\n  876 |     // so cache-busting is via headers only.\n> 877 |     expect(src).toContain(\"'/db/brands?select=name,logo_url&order=sort_order'\");\n      |                 ^\n  878 |     expect(src).toContain(\"'Cache-Control':'no-cache'\");\n  879 |     expect(src).toContain(\"'Pragma':'no-cache'\");\n  880 |   });\n    at /var/www/360lm/tests/recce.spec.js:877:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:46.027Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-7a345-che-Control-no-cache-header-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 877
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-a1692cb7fef046ec5f28",
              "file": "recce.spec.js",
              "line": 871,
              "column": 3
            },
            {
              "title": "SW cache is current recce version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1567,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:735:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 735
                        },
                        "snippet": "  733 |     const resp = await ctx.get(BASE_URL);\n  734 |     const src = await resp.text();\n> 735 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  736 |   });\n  737 |\n  738 |   test('source contains campaign dropdown elements and helpers', async ({}, testInfo) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 735
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  733 |     const resp = await ctx.get(BASE_URL);\n  734 |     const src = await resp.text();\n> 735 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  736 |   });\n  737 |\n  738 |   test('source contains campaign dropdown elements and helpers', async ({}, testInfo) => {\n    at /var/www/360lm/tests/recce.spec.js:735:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:39:15.537Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-7ac21-he-is-current-recce-version-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 735
                      }
                    },
                    {
                      "workerIndex": 1568,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 50,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:735:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 735
                        },
                        "snippet": "  733 |     const resp = await ctx.get(BASE_URL);\n  734 |     const src = await resp.text();\n> 735 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  736 |   });\n  737 |\n  738 |   test('source contains campaign dropdown elements and helpers', async ({}, testInfo) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 735
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  733 |     const resp = await ctx.get(BASE_URL);\n  734 |     const src = await resp.text();\n> 735 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  736 |   });\n  737 |\n  738 |   test('source contains campaign dropdown elements and helpers', async ({}, testInfo) => {\n    at /var/www/360lm/tests/recce.spec.js:735:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:39:16.393Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-7ac21-he-is-current-recce-version-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 735
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-a64aed811a84b53745bd",
              "file": "recce.spec.js",
              "line": 731,
              "column": 3
            },
            {
              "title": "source contains campaign dropdown elements and helpers",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1569,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:742:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 742
                        },
                        "snippet": "  740 |     const resp = await ctx.get(BASE_URL);\n  741 |     const src = await resp.text();\n> 742 |     expect(src).toContain('f-campaign-id');\n      |                 ^\n  743 |     expect(src).toContain('loadCampaignsForForm');\n  744 |     expect(src).toContain('onCampaignSelect');\n  745 |     expect(src).toContain('_notifySupervisor');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 742
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  740 |     const resp = await ctx.get(BASE_URL);\n  741 |     const src = await resp.text();\n> 742 |     expect(src).toContain('f-campaign-id');\n      |                 ^\n  743 |     expect(src).toContain('loadCampaignsForForm');\n  744 |     expect(src).toContain('onCampaignSelect');\n  745 |     expect(src).toContain('_notifySupervisor');\n    at /var/www/360lm/tests/recce.spec.js:742:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:39:17.242Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-24d3f-opdown-elements-and-helpers-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 742
                      }
                    },
                    {
                      "workerIndex": 1570,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 74,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:742:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 742
                        },
                        "snippet": "  740 |     const resp = await ctx.get(BASE_URL);\n  741 |     const src = await resp.text();\n> 742 |     expect(src).toContain('f-campaign-id');\n      |                 ^\n  743 |     expect(src).toContain('loadCampaignsForForm');\n  744 |     expect(src).toContain('onCampaignSelect');\n  745 |     expect(src).toContain('_notifySupervisor');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 742
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"f-campaign-id\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  740 |     const resp = await ctx.get(BASE_URL);\n  741 |     const src = await resp.text();\n> 742 |     expect(src).toContain('f-campaign-id');\n      |                 ^\n  743 |     expect(src).toContain('loadCampaignsForForm');\n  744 |     expect(src).toContain('onCampaignSelect');\n  745 |     expect(src).toContain('_notifySupervisor');\n    at /var/www/360lm/tests/recce.spec.js:742:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:39:18.219Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-24d3f-opdown-elements-and-helpers-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 742
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-1240d0dca373f957be4f",
              "file": "recce.spec.js",
              "line": 738,
              "column": 3
            },
            {
              "title": "campaign dropdown exists in form-store DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1571,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 17264,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:758:50",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 50,
                          "line": 758
                        },
                        "snippet": "  756 |     await page.goto(BASE_URL);\n  757 |     await page.waitForSelector('#f-campaign-id', { timeout: 12000 }).catch(() => {});\n> 758 |     await expect(page.locator('#f-campaign-id')).toHaveCount(1);\n      |                                                  ^\n  759 |   });\n  760 |\n  761 |   test('source contains branding lock banner and override', async ({}, testInfo) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 50,
                            "line": 758
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  756 |     await page.goto(BASE_URL);\n  757 |     await page.waitForSelector('#f-campaign-id', { timeout: 12000 }).catch(() => {});\n> 758 |     await expect(page.locator('#f-campaign-id')).toHaveCount(1);\n      |                                                  ^\n  759 |   });\n  760 |\n  761 |   test('source contains branding lock banner and override', async ({}, testInfo) => {\n    at /var/www/360lm/tests/recce.spec.js:758:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:39:19.234Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4ad13-wn-exists-in-form-store-DOM-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4ad13-wn-exists-in-form-store-DOM-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 50,
                        "line": 758
                      }
                    },
                    {
                      "workerIndex": 1572,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 17282,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:758:50",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 50,
                          "line": 758
                        },
                        "snippet": "  756 |     await page.goto(BASE_URL);\n  757 |     await page.waitForSelector('#f-campaign-id', { timeout: 12000 }).catch(() => {});\n> 758 |     await expect(page.locator('#f-campaign-id')).toHaveCount(1);\n      |                                                  ^\n  759 |   });\n  760 |\n  761 |   test('source contains branding lock banner and override', async ({}, testInfo) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 50,
                            "line": 758
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#f-campaign-id')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#f-campaign-id')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  756 |     await page.goto(BASE_URL);\n  757 |     await page.waitForSelector('#f-campaign-id', { timeout: 12000 }).catch(() => {});\n> 758 |     await expect(page.locator('#f-campaign-id')).toHaveCount(1);\n      |                                                  ^\n  759 |   });\n  760 |\n  761 |   test('source contains branding lock banner and override', async ({}, testInfo) => {\n    at /var/www/360lm/tests/recce.spec.js:758:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:39:37.507Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4ad13-wn-exists-in-form-store-DOM-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4ad13-wn-exists-in-form-store-DOM-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 50,
                        "line": 758
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-5ce6795d53d13d9c9eda",
              "file": "recce.spec.js",
              "line": 750,
              "column": 3
            },
            {
              "title": "source contains branding lock banner and override",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1573,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:765:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 765
                        },
                        "snippet": "  763 |     const resp = await ctx.get(BASE_URL);\n  764 |     const src = await resp.text();\n> 765 |     expect(src).toContain('branding-lock-banner');\n      |                 ^\n  766 |     expect(src).toContain('branding-override-wrap');\n  767 |     expect(src).toContain('_brandingLocked');\n  768 |     expect(src).toContain('unlockBrandingOverride');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 765
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  763 |     const resp = await ctx.get(BASE_URL);\n  764 |     const src = await resp.text();\n> 765 |     expect(src).toContain('branding-lock-banner');\n      |                 ^\n  766 |     expect(src).toContain('branding-override-wrap');\n  767 |     expect(src).toContain('_brandingLocked');\n  768 |     expect(src).toContain('unlockBrandingOverride');\n    at /var/www/360lm/tests/recce.spec.js:765:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:39:55.831Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-93c46-ng-lock-banner-and-override-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 765
                      }
                    },
                    {
                      "workerIndex": 1574,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:765:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 765
                        },
                        "snippet": "  763 |     const resp = await ctx.get(BASE_URL);\n  764 |     const src = await resp.text();\n> 765 |     expect(src).toContain('branding-lock-banner');\n      |                 ^\n  766 |     expect(src).toContain('branding-override-wrap');\n  767 |     expect(src).toContain('_brandingLocked');\n  768 |     expect(src).toContain('unlockBrandingOverride');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 765
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"branding-lock-banner\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  763 |     const resp = await ctx.get(BASE_URL);\n  764 |     const src = await resp.text();\n> 765 |     expect(src).toContain('branding-lock-banner');\n      |                 ^\n  766 |     expect(src).toContain('branding-override-wrap');\n  767 |     expect(src).toContain('_brandingLocked');\n  768 |     expect(src).toContain('unlockBrandingOverride');\n    at /var/www/360lm/tests/recce.spec.js:765:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:39:56.750Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-93c46-ng-lock-banner-and-override-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 765
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-516c8968531fbd7394b3",
              "file": "recce.spec.js",
              "line": 761,
              "column": 3
            },
            {
              "title": "branding lock banner is in form-branding view DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1575,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5632,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:775:57",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 57,
                          "line": 775
                        },
                        "snippet": "  773 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  774 |     await page.goto(BASE_URL);\n> 775 |     await expect(page.locator('#branding-lock-banner')).toHaveCount(1);\n      |                                                         ^\n  776 |     // Hidden by default (not locked on fresh load)\n  777 |     await expect(page.locator('#branding-lock-banner')).toBeHidden();\n  778 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 57,
                            "line": 775
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  773 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  774 |     await page.goto(BASE_URL);\n> 775 |     await expect(page.locator('#branding-lock-banner')).toHaveCount(1);\n      |                                                         ^\n  776 |     // Hidden by default (not locked on fresh load)\n  777 |     await expect(page.locator('#branding-lock-banner')).toBeHidden();\n  778 |   });\n    at /var/www/360lm/tests/recce.spec.js:775:57"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:39:57.697Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-ac83e-s-in-form-branding-view-DOM-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-ac83e-s-in-form-branding-view-DOM-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 57,
                        "line": 775
                      }
                    },
                    {
                      "workerIndex": 1576,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5324,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:775:57",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 57,
                          "line": 775
                        },
                        "snippet": "  773 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  774 |     await page.goto(BASE_URL);\n> 775 |     await expect(page.locator('#branding-lock-banner')).toHaveCount(1);\n      |                                                         ^\n  776 |     // Hidden by default (not locked on fresh load)\n  777 |     await expect(page.locator('#branding-lock-banner')).toBeHidden();\n  778 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 57,
                            "line": 775
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#branding-lock-banner')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#branding-lock-banner')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  773 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  774 |     await page.goto(BASE_URL);\n> 775 |     await expect(page.locator('#branding-lock-banner')).toHaveCount(1);\n      |                                                         ^\n  776 |     // Hidden by default (not locked on fresh load)\n  777 |     await expect(page.locator('#branding-lock-banner')).toBeHidden();\n  778 |   });\n    at /var/www/360lm/tests/recce.spec.js:775:57"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:05.641Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-ac83e-s-in-form-branding-view-DOM-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-ac83e-s-in-form-branding-view-DOM-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 57,
                        "line": 775
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-de584f8992be3a97d201",
              "file": "recce.spec.js",
              "line": 772,
              "column": 3
            },
            {
              "title": "source contains pending job tab, link job modal, archiving",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1577,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 65,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:784:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 784
                        },
                        "snippet": "  782 |     const resp = await ctx.get(BASE_URL);\n  783 |     const src = await resp.text();\n> 784 |     expect(src).toContain('pending-job-btn');\n      |                 ^\n  785 |     expect(src).toContain('togglePendingJobView');\n  786 |     expect(src).toContain('openLinkJobModal');\n  787 |     expect(src).toContain('modal-link-job');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 784
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  782 |     const resp = await ctx.get(BASE_URL);\n  783 |     const src = await resp.text();\n> 784 |     expect(src).toContain('pending-job-btn');\n      |                 ^\n  785 |     expect(src).toContain('togglePendingJobView');\n  786 |     expect(src).toContain('openLinkJobModal');\n  787 |     expect(src).toContain('modal-link-job');\n    at /var/www/360lm/tests/recce.spec.js:784:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:12.040Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-418f8-ab-link-job-modal-archiving-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 784
                      }
                    },
                    {
                      "workerIndex": 1578,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:784:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 784
                        },
                        "snippet": "  782 |     const resp = await ctx.get(BASE_URL);\n  783 |     const src = await resp.text();\n> 784 |     expect(src).toContain('pending-job-btn');\n      |                 ^\n  785 |     expect(src).toContain('togglePendingJobView');\n  786 |     expect(src).toContain('openLinkJobModal');\n  787 |     expect(src).toContain('modal-link-job');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 784
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"pending-job-btn\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  782 |     const resp = await ctx.get(BASE_URL);\n  783 |     const src = await resp.text();\n> 784 |     expect(src).toContain('pending-job-btn');\n      |                 ^\n  785 |     expect(src).toContain('togglePendingJobView');\n  786 |     expect(src).toContain('openLinkJobModal');\n  787 |     expect(src).toContain('modal-link-job');\n    at /var/www/360lm/tests/recce.spec.js:784:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:12.985Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-418f8-ab-link-job-modal-archiving-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 784
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-8a3e6b485986ac3c62f5",
              "file": "recce.spec.js",
              "line": 780,
              "column": 3
            },
            {
              "title": "source contains client_status admin badges",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1579,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 83,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:797:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 797
                        },
                        "snippet": "  795 |     const resp = await ctx.get(BASE_URL);\n  796 |     const src = await resp.text();\n> 797 |     expect(src).toContain('clientStatusBadge');\n      |                 ^\n  798 |     expect(src).toContain('archiveBadge');\n  799 |     expect(src).toContain('canArchive');\n  800 |     expect(src).toContain('client_archived');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 797
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  795 |     const resp = await ctx.get(BASE_URL);\n  796 |     const src = await resp.text();\n> 797 |     expect(src).toContain('clientStatusBadge');\n      |                 ^\n  798 |     expect(src).toContain('archiveBadge');\n  799 |     expect(src).toContain('canArchive');\n  800 |     expect(src).toContain('client_archived');\n    at /var/www/360lm/tests/recce.spec.js:797:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:13.924Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b4e29--client-status-admin-badges-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 797
                      }
                    },
                    {
                      "workerIndex": 1580,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:797:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 797
                        },
                        "snippet": "  795 |     const resp = await ctx.get(BASE_URL);\n  796 |     const src = await resp.text();\n> 797 |     expect(src).toContain('clientStatusBadge');\n      |                 ^\n  798 |     expect(src).toContain('archiveBadge');\n  799 |     expect(src).toContain('canArchive');\n  800 |     expect(src).toContain('client_archived');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 797
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"clientStatusBadge\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  795 |     const resp = await ctx.get(BASE_URL);\n  796 |     const src = await resp.text();\n> 797 |     expect(src).toContain('clientStatusBadge');\n      |                 ^\n  798 |     expect(src).toContain('archiveBadge');\n  799 |     expect(src).toContain('canArchive');\n  800 |     expect(src).toContain('client_archived');\n    at /var/www/360lm/tests/recce.spec.js:797:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:14.841Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b4e29--client-status-admin-badges-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 797
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-af57bdb1891f67db09a6",
              "file": "recce.spec.js",
              "line": 793,
              "column": 3
            },
            {
              "title": "link-job modal exists in DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1581,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5263,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:807:51",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 51,
                          "line": 807
                        },
                        "snippet": "  805 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  806 |     await page.goto(BASE_URL);\n> 807 |     await expect(page.locator('#modal-link-job')).toHaveCount(1);\n      |                                                   ^\n  808 |     await expect(page.locator('#link-job-sel')).toHaveCount(1);\n  809 |   });\n  810 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 51,
                            "line": 807
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  805 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  806 |     await page.goto(BASE_URL);\n> 807 |     await expect(page.locator('#modal-link-job')).toHaveCount(1);\n      |                                                   ^\n  808 |     await expect(page.locator('#link-job-sel')).toHaveCount(1);\n  809 |   });\n  810 |\n    at /var/www/360lm/tests/recce.spec.js:807:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:15.695Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-2f360-ink-job-modal-exists-in-DOM-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-2f360-ink-job-modal-exists-in-DOM-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 51,
                        "line": 807
                      }
                    },
                    {
                      "workerIndex": 1582,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5269,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/recce.spec.js:807:51",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 51,
                          "line": 807
                        },
                        "snippet": "  805 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  806 |     await page.goto(BASE_URL);\n> 807 |     await expect(page.locator('#modal-link-job')).toHaveCount(1);\n      |                                                   ^\n  808 |     await expect(page.locator('#link-job-sel')).toHaveCount(1);\n  809 |   });\n  810 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 51,
                            "line": 807
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#modal-link-job')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#modal-link-job')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  805 |     await injectSession(page);  // v38 fix: without session, Recce redirects to hub login\n  806 |     await page.goto(BASE_URL);\n> 807 |     await expect(page.locator('#modal-link-job')).toHaveCount(1);\n      |                                                   ^\n  808 |     await expect(page.locator('#link-job-sel')).toHaveCount(1);\n  809 |   });\n  810 |\n    at /var/www/360lm/tests/recce.spec.js:807:51"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:21.937Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-2f360-ink-job-modal-exists-in-DOM-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-2f360-ink-job-modal-exists-in-DOM-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 51,
                        "line": 807
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-305385ad33d15afa5b67",
              "file": "recce.spec.js",
              "line": 804,
              "column": 3
            },
            {
              "title": "v38: refreshSharedData helper exists",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1583,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 49,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:816:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 816
                        },
                        "snippet": "  814 |     const resp = await ctx.get(BASE_URL);\n  815 |     const src = await resp.text();\n> 816 |     expect(src).toContain('async function refreshSharedData');\n      |                 ^\n  817 |     expect(src).toContain('async function refreshStoresFromUrl');\n  818 |   });\n  819 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 816
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  814 |     const resp = await ctx.get(BASE_URL);\n  815 |     const src = await resp.text();\n> 816 |     expect(src).toContain('async function refreshSharedData');\n      |                 ^\n  817 |     expect(src).toContain('async function refreshStoresFromUrl');\n  818 |   });\n  819 |\n    at /var/www/360lm/tests/recce.spec.js:816:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:28.185Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-042e4-eshSharedData-helper-exists-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 816
                      }
                    },
                    {
                      "workerIndex": 1584,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:816:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 816
                        },
                        "snippet": "  814 |     const resp = await ctx.get(BASE_URL);\n  815 |     const src = await resp.text();\n> 816 |     expect(src).toContain('async function refreshSharedData');\n      |                 ^\n  817 |     expect(src).toContain('async function refreshStoresFromUrl');\n  818 |   });\n  819 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 816
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function refreshSharedData\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  814 |     const resp = await ctx.get(BASE_URL);\n  815 |     const src = await resp.text();\n> 816 |     expect(src).toContain('async function refreshSharedData');\n      |                 ^\n  817 |     expect(src).toContain('async function refreshStoresFromUrl');\n  818 |   });\n  819 |\n    at /var/www/360lm/tests/recce.spec.js:816:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:29.081Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-042e4-eshSharedData-helper-exists-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 816
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-a48fd5993155f1117bec",
              "file": "recce.spec.js",
              "line": 812,
              "column": 3
            },
            {
              "title": "v38: refreshSharedData is called from startNewRecce",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1585,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:825:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 825
                        },
                        "snippet": "  823 |     const src = await resp.text();\n  824 |     // startNewRecce is now async and awaits refreshSharedData (with 2.5s race)\n> 825 |     expect(src).toContain('async function startNewRecce');\n      |                 ^\n  826 |     expect(src).toMatch(/startNewRecce[\\s\\S]*?refreshSharedData/);\n  827 |   });\n  828 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 825
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  823 |     const src = await resp.text();\n  824 |     // startNewRecce is now async and awaits refreshSharedData (with 2.5s race)\n> 825 |     expect(src).toContain('async function startNewRecce');\n      |                 ^\n  826 |     expect(src).toMatch(/startNewRecce[\\s\\S]*?refreshSharedData/);\n  827 |   });\n  828 |\n    at /var/www/360lm/tests/recce.spec.js:825:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:29.981Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-d2808-s-called-from-startNewRecce-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 825
                      }
                    },
                    {
                      "workerIndex": 1586,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:825:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 825
                        },
                        "snippet": "  823 |     const src = await resp.text();\n  824 |     // startNewRecce is now async and awaits refreshSharedData (with 2.5s race)\n> 825 |     expect(src).toContain('async function startNewRecce');\n      |                 ^\n  826 |     expect(src).toMatch(/startNewRecce[\\s\\S]*?refreshSharedData/);\n  827 |   });\n  828 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 825
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"async function startNewRecce\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  823 |     const src = await resp.text();\n  824 |     // startNewRecce is now async and awaits refreshSharedData (with 2.5s race)\n> 825 |     expect(src).toContain('async function startNewRecce');\n      |                 ^\n  826 |     expect(src).toMatch(/startNewRecce[\\s\\S]*?refreshSharedData/);\n  827 |   });\n  828 |\n    at /var/www/360lm/tests/recce.spec.js:825:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:30.963Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-d2808-s-called-from-startNewRecce-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 825
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-3a10d26e924340dcfa0d",
              "file": "recce.spec.js",
              "line": 820,
              "column": 3
            },
            {
              "title": "v38: loadBrandsFromDb no longer has extras merge leak",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1587,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:838:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 838
                        },
                        "snippet": "  836 |     expect(src).not.toContain('brands=[...dbNames,...extras]');\n  837 |     // New canonical pattern present\n> 838 |     expect(src).toContain('single source of truth');\n      |                 ^\n  839 |     expect(src).toContain(\"brands=dbNames.length?dbNames:['PHILIPS']\");\n  840 |   });\n  841 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 838
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  836 |     expect(src).not.toContain('brands=[...dbNames,...extras]');\n  837 |     // New canonical pattern present\n> 838 |     expect(src).toContain('single source of truth');\n      |                 ^\n  839 |     expect(src).toContain(\"brands=dbNames.length?dbNames:['PHILIPS']\");\n  840 |   });\n  841 |\n    at /var/www/360lm/tests/recce.spec.js:838:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:31.921Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b9a93-onger-has-extras-merge-leak-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 838
                      }
                    },
                    {
                      "workerIndex": 1588,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 65,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:838:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 838
                        },
                        "snippet": "  836 |     expect(src).not.toContain('brands=[...dbNames,...extras]');\n  837 |     // New canonical pattern present\n> 838 |     expect(src).toContain('single source of truth');\n      |                 ^\n  839 |     expect(src).toContain(\"brands=dbNames.length?dbNames:['PHILIPS']\");\n  840 |   });\n  841 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 838
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"single source of truth\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  836 |     expect(src).not.toContain('brands=[...dbNames,...extras]');\n  837 |     // New canonical pattern present\n> 838 |     expect(src).toContain('single source of truth');\n      |                 ^\n  839 |     expect(src).toContain(\"brands=dbNames.length?dbNames:['PHILIPS']\");\n  840 |   });\n  841 |\n    at /var/www/360lm/tests/recce.spec.js:838:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:32.799Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b9a93-onger-has-extras-merge-leak-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 838
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-fdf858a91bcf03f61cd7",
              "file": "recce.spec.js",
              "line": 829,
              "column": 3
            },
            {
              "title": "v38: addBrandToList verifies the DB write before claiming success",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1589,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:846:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 846
                        },
                        "snippet": "  844 |     const resp = await ctx.get(BASE_URL);\n  845 |     const src = await resp.text();\n> 846 |     expect(src).toContain('VERIFY the row actually landed');\n      |                 ^\n  847 |     expect(src).toContain('Write not visible in DB');\n  848 |     expect(src).toContain('visible to all users');\n  849 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 846
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  844 |     const resp = await ctx.get(BASE_URL);\n  845 |     const src = await resp.text();\n> 846 |     expect(src).toContain('VERIFY the row actually landed');\n      |                 ^\n  847 |     expect(src).toContain('Write not visible in DB');\n  848 |     expect(src).toContain('visible to all users');\n  849 |   });\n    at /var/www/360lm/tests/recce.spec.js:846:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:33.658Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-cce73-ite-before-claiming-success-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 846
                      }
                    },
                    {
                      "workerIndex": 1590,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:846:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 846
                        },
                        "snippet": "  844 |     const resp = await ctx.get(BASE_URL);\n  845 |     const src = await resp.text();\n> 846 |     expect(src).toContain('VERIFY the row actually landed');\n      |                 ^\n  847 |     expect(src).toContain('Write not visible in DB');\n  848 |     expect(src).toContain('visible to all users');\n  849 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 846
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"VERIFY the row actually landed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  844 |     const resp = await ctx.get(BASE_URL);\n  845 |     const src = await resp.text();\n> 846 |     expect(src).toContain('VERIFY the row actually landed');\n      |                 ^\n  847 |     expect(src).toContain('Write not visible in DB');\n  848 |     expect(src).toContain('visible to all users');\n  849 |   });\n    at /var/www/360lm/tests/recce.spec.js:846:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:34.510Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-cce73-ite-before-claiming-success-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 846
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-d873d54c12092e587f8b",
              "file": "recce.spec.js",
              "line": 842,
              "column": 3
            },
            {
              "title": "v38: fetchServerBrands backdoor disabled",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1591,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 48,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:857:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 857
                        },
                        "snippet": "  855 |     // The function exists as a stub that shows an error toast,\n  856 |     // but the body must NOT do a local write anymore\n> 857 |     expect(src).toContain('fetchServerBrands removed');\n      |                 ^\n  858 |     expect(src).toContain('This feature has been removed');\n  859 |     expect(src).not.toContain('brands=list.map(b=>b.toString().toUpperCase())');\n  860 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 857
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  855 |     // The function exists as a stub that shows an error toast,\n  856 |     // but the body must NOT do a local write anymore\n> 857 |     expect(src).toContain('fetchServerBrands removed');\n      |                 ^\n  858 |     expect(src).toContain('This feature has been removed');\n  859 |     expect(src).not.toContain('brands=list.map(b=>b.toString().toUpperCase())');\n  860 |   });\n    at /var/www/360lm/tests/recce.spec.js:857:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:35.353Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-471a3-verBrands-backdoor-disabled-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 857
                      }
                    },
                    {
                      "workerIndex": 1592,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:857:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 857
                        },
                        "snippet": "  855 |     // The function exists as a stub that shows an error toast,\n  856 |     // but the body must NOT do a local write anymore\n> 857 |     expect(src).toContain('fetchServerBrands removed');\n      |                 ^\n  858 |     expect(src).toContain('This feature has been removed');\n  859 |     expect(src).not.toContain('brands=list.map(b=>b.toString().toUpperCase())');\n  860 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 857
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetchServerBrands removed\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  855 |     // The function exists as a stub that shows an error toast,\n  856 |     // but the body must NOT do a local write anymore\n> 857 |     expect(src).toContain('fetchServerBrands removed');\n      |                 ^\n  858 |     expect(src).toContain('This feature has been removed');\n  859 |     expect(src).not.toContain('brands=list.map(b=>b.toString().toUpperCase())');\n  860 |   });\n    at /var/www/360lm/tests/recce.spec.js:857:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:36.311Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-471a3-verBrands-backdoor-disabled-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 857
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-1676f605732367303771",
              "file": "recce.spec.js",
              "line": 851,
              "column": 3
            },
            {
              "title": "v38: admin filter bar has Config button for supervisors",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1593,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:866:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 866
                        },
                        "snippet": "  864 |     const resp = await ctx.get(BASE_URL);\n  865 |     const src = await resp.text();\n> 866 |     expect(src).toContain('id=\"admin-config-btn\"');\n      |                 ^\n  867 |     expect(src).toContain(\"onclick=\\\"showView('settings')\\\"\");\n  868 |     expect(src).toContain('Brand / Client / Store config');\n  869 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 866
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  864 |     const resp = await ctx.get(BASE_URL);\n  865 |     const src = await resp.text();\n> 866 |     expect(src).toContain('id=\"admin-config-btn\"');\n      |                 ^\n  867 |     expect(src).toContain(\"onclick=\\\"showView('settings')\\\"\");\n  868 |     expect(src).toContain('Brand / Client / Store config');\n  869 |   });\n    at /var/www/360lm/tests/recce.spec.js:866:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:37.178Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-8700d-nfig-button-for-supervisors-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 866
                      }
                    },
                    {
                      "workerIndex": 1594,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 50,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:866:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 866
                        },
                        "snippet": "  864 |     const resp = await ctx.get(BASE_URL);\n  865 |     const src = await resp.text();\n> 866 |     expect(src).toContain('id=\"admin-config-btn\"');\n      |                 ^\n  867 |     expect(src).toContain(\"onclick=\\\"showView('settings')\\\"\");\n  868 |     expect(src).toContain('Brand / Client / Store config');\n  869 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 866
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"admin-config-btn\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  864 |     const resp = await ctx.get(BASE_URL);\n  865 |     const src = await resp.text();\n> 866 |     expect(src).toContain('id=\"admin-config-btn\"');\n      |                 ^\n  867 |     expect(src).toContain(\"onclick=\\\"showView('settings')\\\"\");\n  868 |     expect(src).toContain('Brand / Client / Store config');\n  869 |   });\n    at /var/www/360lm/tests/recce.spec.js:866:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:38.095Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-8700d-nfig-button-for-supervisors-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 866
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-24595ac2a198f1d66a9c",
              "file": "recce.spec.js",
              "line": 862,
              "column": 3
            },
            {
              "title": "v38: DB fetches use Cache-Control no-cache header",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1595,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:877:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 877
                        },
                        "snippet": "  875 |     // PostgREST rejects unknown query params (would treat &t= as column filter),\n  876 |     // so cache-busting is via headers only.\n> 877 |     expect(src).toContain(\"'/db/brands?select=name,logo_url&order=sort_order'\");\n      |                 ^\n  878 |     expect(src).toContain(\"'Cache-Control':'no-cache'\");\n  879 |     expect(src).toContain(\"'Pragma':'no-cache'\");\n  880 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 877
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  875 |     // PostgREST rejects unknown query params (would treat &t= as column filter),\n  876 |     // so cache-busting is via headers only.\n> 877 |     expect(src).toContain(\"'/db/brands?select=name,logo_url&order=sort_order'\");\n      |                 ^\n  878 |     expect(src).toContain(\"'Cache-Control':'no-cache'\");\n  879 |     expect(src).toContain(\"'Pragma':'no-cache'\");\n  880 |   });\n    at /var/www/360lm/tests/recce.spec.js:877:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:38.954Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-7a345-che-Control-no-cache-header-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 877
                      }
                    },
                    {
                      "workerIndex": 1596,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:877:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 877
                        },
                        "snippet": "  875 |     // PostgREST rejects unknown query params (would treat &t= as column filter),\n  876 |     // so cache-busting is via headers only.\n> 877 |     expect(src).toContain(\"'/db/brands?select=name,logo_url&order=sort_order'\");\n      |                 ^\n  878 |     expect(src).toContain(\"'Cache-Control':'no-cache'\");\n  879 |     expect(src).toContain(\"'Pragma':'no-cache'\");\n  880 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 877
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'/db/brands?select=name,logo_url&order=sort_order'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  875 |     // PostgREST rejects unknown query params (would treat &t= as column filter),\n  876 |     // so cache-busting is via headers only.\n> 877 |     expect(src).toContain(\"'/db/brands?select=name,logo_url&order=sort_order'\");\n      |                 ^\n  878 |     expect(src).toContain(\"'Cache-Control':'no-cache'\");\n  879 |     expect(src).toContain(\"'Pragma':'no-cache'\");\n  880 |   });\n    at /var/www/360lm/tests/recce.spec.js:877:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:39.851Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-7a345-che-Control-no-cache-header-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 877
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-058abd0c019c2fd23981",
              "file": "recce.spec.js",
              "line": 871,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — v16 Client Approval Admin",
          "file": "recce.spec.js",
          "line": 884,
          "column": 6,
          "specs": [
            {
              "title": "SW cache is current recce version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 590,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:889:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 889
                        },
                        "snippet": "  887 |     const resp = await ctx.get(BASE_URL);\n  888 |     const src = await resp.text();\n> 889 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  890 |   });\n  891 |\n  892 |   test('source contains v16 admin overlay HTML + JS symbols', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 889
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  887 |     const resp = await ctx.get(BASE_URL);\n  888 |     const src = await resp.text();\n> 889 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  890 |   });\n  891 |\n  892 |   test('source contains v16 admin overlay HTML + JS symbols', async ({}) => {\n    at /var/www/360lm/tests/recce.spec.js:889:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:46.889Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-3763e-he-is-current-recce-version-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 889
                      }
                    },
                    {
                      "workerIndex": 591,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:889:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 889
                        },
                        "snippet": "  887 |     const resp = await ctx.get(BASE_URL);\n  888 |     const src = await resp.text();\n> 889 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  890 |   });\n  891 |\n  892 |   test('source contains v16 admin overlay HTML + JS symbols', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 889
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  887 |     const resp = await ctx.get(BASE_URL);\n  888 |     const src = await resp.text();\n> 889 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  890 |   });\n  891 |\n  892 |   test('source contains v16 admin overlay HTML + JS symbols', async ({}) => {\n    at /var/www/360lm/tests/recce.spec.js:889:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:47.793Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-3763e-he-is-current-recce-version-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 889
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-8f4317f08b90afd689ac",
              "file": "recce.spec.js",
              "line": 885,
              "column": 3
            },
            {
              "title": "source contains v16 admin overlay HTML + JS symbols",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 592,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 49,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:896:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 896
                        },
                        "snippet": "  894 |     const resp = await ctx.get(BASE_URL);\n  895 |     const src = await resp.text();\n> 896 |     expect(src).toContain('id=\"ardl-bg\"');\n      |                 ^\n  897 |     expect(src).toContain('id=\"inv-mini-bg\"');\n  898 |     expect(src).toContain('function openAdminRecce');\n  899 |     expect(src).toContain('function markOfficialFromUI');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 896
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  894 |     const resp = await ctx.get(BASE_URL);\n  895 |     const src = await resp.text();\n> 896 |     expect(src).toContain('id=\"ardl-bg\"');\n      |                 ^\n  897 |     expect(src).toContain('id=\"inv-mini-bg\"');\n  898 |     expect(src).toContain('function openAdminRecce');\n  899 |     expect(src).toContain('function markOfficialFromUI');\n    at /var/www/360lm/tests/recce.spec.js:896:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:48.722Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b46ba-min-overlay-HTML-JS-symbols-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 896
                      }
                    },
                    {
                      "workerIndex": 593,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:896:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 896
                        },
                        "snippet": "  894 |     const resp = await ctx.get(BASE_URL);\n  895 |     const src = await resp.text();\n> 896 |     expect(src).toContain('id=\"ardl-bg\"');\n      |                 ^\n  897 |     expect(src).toContain('id=\"inv-mini-bg\"');\n  898 |     expect(src).toContain('function openAdminRecce');\n  899 |     expect(src).toContain('function markOfficialFromUI');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 896
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  894 |     const resp = await ctx.get(BASE_URL);\n  895 |     const src = await resp.text();\n> 896 |     expect(src).toContain('id=\"ardl-bg\"');\n      |                 ^\n  897 |     expect(src).toContain('id=\"inv-mini-bg\"');\n  898 |     expect(src).toContain('function openAdminRecce');\n  899 |     expect(src).toContain('function markOfficialFromUI');\n    at /var/www/360lm/tests/recce.spec.js:896:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:49.703Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b46ba-min-overlay-HTML-JS-symbols-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 896
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-eefe8ec8044776aca215",
              "file": "recce.spec.js",
              "line": 892,
              "column": 3
            },
            {
              "title": "css classes for client status pill + deprecated badge present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 594,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:909:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 909
                        },
                        "snippet": "  907 |     const resp = await ctx.get(BASE_URL);\n  908 |     const src = await resp.text();\n> 909 |     expect(src).toContain('.csl-pill');\n      |                 ^\n  910 |     expect(src).toContain('.csl-pill.approved');\n  911 |     expect(src).toContain('.csl-pill.approved_with_comments');\n  912 |     expect(src).toContain('.csl-pill.resubmission_required');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 909
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  907 |     const resp = await ctx.get(BASE_URL);\n  908 |     const src = await resp.text();\n> 909 |     expect(src).toContain('.csl-pill');\n      |                 ^\n  910 |     expect(src).toContain('.csl-pill.approved');\n  911 |     expect(src).toContain('.csl-pill.approved_with_comments');\n  912 |     expect(src).toContain('.csl-pill.resubmission_required');\n    at /var/www/360lm/tests/recce.spec.js:909:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:50.602Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4049c-ll-deprecated-badge-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 909
                      }
                    },
                    {
                      "workerIndex": 595,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:909:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 909
                        },
                        "snippet": "  907 |     const resp = await ctx.get(BASE_URL);\n  908 |     const src = await resp.text();\n> 909 |     expect(src).toContain('.csl-pill');\n      |                 ^\n  910 |     expect(src).toContain('.csl-pill.approved');\n  911 |     expect(src).toContain('.csl-pill.approved_with_comments');\n  912 |     expect(src).toContain('.csl-pill.resubmission_required');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 909
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  907 |     const resp = await ctx.get(BASE_URL);\n  908 |     const src = await resp.text();\n> 909 |     expect(src).toContain('.csl-pill');\n      |                 ^\n  910 |     expect(src).toContain('.csl-pill.approved');\n  911 |     expect(src).toContain('.csl-pill.approved_with_comments');\n  912 |     expect(src).toContain('.csl-pill.resubmission_required');\n    at /var/www/360lm/tests/recce.spec.js:909:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:51.587Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4049c-ll-deprecated-badge-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 909
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-5db7fe97d8a0cebab5e3",
              "file": "recce.spec.js",
              "line": 905,
              "column": 3
            },
            {
              "title": "en + hi translation keys for v16 surface present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 596,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:924:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 924
                        },
                        "snippet": "  922 |     const src = await resp.text();\n  923 |     // en\n> 924 |     expect(src).toContain(\"av_csl_pending: 'Pending'\");\n      |                 ^\n  925 |     expect(src).toContain(\"av_csl_approved: 'Approved'\");\n  926 |     expect(src).toContain(\"av_ardl_lock_banner:\");\n  927 |     expect(src).toContain(\"av_ardl_mark_off:\");"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 924
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  922 |     const src = await resp.text();\n  923 |     // en\n> 924 |     expect(src).toContain(\"av_csl_pending: 'Pending'\");\n      |                 ^\n  925 |     expect(src).toContain(\"av_csl_approved: 'Approved'\");\n  926 |     expect(src).toContain(\"av_ardl_lock_banner:\");\n  927 |     expect(src).toContain(\"av_ardl_mark_off:\");\n    at /var/www/360lm/tests/recce.spec.js:924:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:52.529Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-1a6d9-eys-for-v16-surface-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 924
                      }
                    },
                    {
                      "workerIndex": 597,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 50,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:924:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 924
                        },
                        "snippet": "  922 |     const src = await resp.text();\n  923 |     // en\n> 924 |     expect(src).toContain(\"av_csl_pending: 'Pending'\");\n      |                 ^\n  925 |     expect(src).toContain(\"av_csl_approved: 'Approved'\");\n  926 |     expect(src).toContain(\"av_ardl_lock_banner:\");\n  927 |     expect(src).toContain(\"av_ardl_mark_off:\");"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 924
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  922 |     const src = await resp.text();\n  923 |     // en\n> 924 |     expect(src).toContain(\"av_csl_pending: 'Pending'\");\n      |                 ^\n  925 |     expect(src).toContain(\"av_csl_approved: 'Approved'\");\n  926 |     expect(src).toContain(\"av_ardl_lock_banner:\");\n  927 |     expect(src).toContain(\"av_ardl_mark_off:\");\n    at /var/www/360lm/tests/recce.spec.js:924:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:53.432Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-1a6d9-eys-for-v16-surface-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 924
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-69535df43358893aac28",
              "file": "recce.spec.js",
              "line": 919,
              "column": 3
            },
            {
              "title": "client_visible_recces_v exposes superseded_at + is_official_for_client",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 598,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 572,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:54.275Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-5a13f77daf37b9fd256b",
              "file": "recce.spec.js",
              "line": 937,
              "column": 3
            },
            {
              "title": "recce-client renders deprecated badge + dep-banner CSS",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 598,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 22,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:954:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 954
                        },
                        "snippet": "  952 |     const resp = await request.get('https://srv1111289.hstgr.cloud/recce-client/');\n  953 |     const src = await resp.text();\n> 954 |     expect(src).toMatch(/recce-client-v\\d+/);\n      |                 ^\n  955 |     expect(src).toContain('.status-pill.deprecated');\n  956 |     expect(src).toContain('.dep-banner');\n  957 |     expect(src).toContain('id=\"v-dep-banner\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 954
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  952 |     const resp = await request.get('https://srv1111289.hstgr.cloud/recce-client/');\n  953 |     const src = await resp.text();\n> 954 |     expect(src).toMatch(/recce-client-v\\d+/);\n      |                 ^\n  955 |     expect(src).toContain('.status-pill.deprecated');\n  956 |     expect(src).toContain('.dep-banner');\n  957 |     expect(src).toContain('id=\"v-dep-banner\"');\n    at /var/www/360lm/tests/recce.spec.js:954:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:54.891Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-c7116-ecated-badge-dep-banner-CSS-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 954
                      }
                    },
                    {
                      "workerIndex": 599,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 71,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:954:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 954
                        },
                        "snippet": "  952 |     const resp = await request.get('https://srv1111289.hstgr.cloud/recce-client/');\n  953 |     const src = await resp.text();\n> 954 |     expect(src).toMatch(/recce-client-v\\d+/);\n      |                 ^\n  955 |     expect(src).toContain('.status-pill.deprecated');\n  956 |     expect(src).toContain('.dep-banner');\n  957 |     expect(src).toContain('id=\"v-dep-banner\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 954
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  952 |     const resp = await request.get('https://srv1111289.hstgr.cloud/recce-client/');\n  953 |     const src = await resp.text();\n> 954 |     expect(src).toMatch(/recce-client-v\\d+/);\n      |                 ^\n  955 |     expect(src).toContain('.status-pill.deprecated');\n  956 |     expect(src).toContain('.dep-banner');\n  957 |     expect(src).toContain('id=\"v-dep-banner\"');\n    at /var/www/360lm/tests/recce.spec.js:954:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:40:55.798Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-c7116-ecated-badge-dep-banner-CSS-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 954
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-a333ce01af111742befd",
              "file": "recce.spec.js",
              "line": 951,
              "column": 3
            },
            {
              "title": "SW cache is current recce version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1597,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:889:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 889
                        },
                        "snippet": "  887 |     const resp = await ctx.get(BASE_URL);\n  888 |     const src = await resp.text();\n> 889 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  890 |   });\n  891 |\n  892 |   test('source contains v16 admin overlay HTML + JS symbols', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 889
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  887 |     const resp = await ctx.get(BASE_URL);\n  888 |     const src = await resp.text();\n> 889 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  890 |   });\n  891 |\n  892 |   test('source contains v16 admin overlay HTML + JS symbols', async ({}) => {\n    at /var/www/360lm/tests/recce.spec.js:889:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:40.759Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-3763e-he-is-current-recce-version-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 889
                      }
                    },
                    {
                      "workerIndex": 1598,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:889:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 889
                        },
                        "snippet": "  887 |     const resp = await ctx.get(BASE_URL);\n  888 |     const src = await resp.text();\n> 889 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  890 |   });\n  891 |\n  892 |   test('source contains v16 admin overlay HTML + JS symbols', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 889
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/const C='recce-v\\d+'/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  887 |     const resp = await ctx.get(BASE_URL);\n  888 |     const src = await resp.text();\n> 889 |     expect(src).toMatch(/const C='recce-v\\d+'/);\n      |                 ^\n  890 |   });\n  891 |\n  892 |   test('source contains v16 admin overlay HTML + JS symbols', async ({}) => {\n    at /var/www/360lm/tests/recce.spec.js:889:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:41.635Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-3763e-he-is-current-recce-version-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 889
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-75d14a2d00cfa3982e0c",
              "file": "recce.spec.js",
              "line": 885,
              "column": 3
            },
            {
              "title": "source contains v16 admin overlay HTML + JS symbols",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1599,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:896:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 896
                        },
                        "snippet": "  894 |     const resp = await ctx.get(BASE_URL);\n  895 |     const src = await resp.text();\n> 896 |     expect(src).toContain('id=\"ardl-bg\"');\n      |                 ^\n  897 |     expect(src).toContain('id=\"inv-mini-bg\"');\n  898 |     expect(src).toContain('function openAdminRecce');\n  899 |     expect(src).toContain('function markOfficialFromUI');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 896
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  894 |     const resp = await ctx.get(BASE_URL);\n  895 |     const src = await resp.text();\n> 896 |     expect(src).toContain('id=\"ardl-bg\"');\n      |                 ^\n  897 |     expect(src).toContain('id=\"inv-mini-bg\"');\n  898 |     expect(src).toContain('function openAdminRecce');\n  899 |     expect(src).toContain('function markOfficialFromUI');\n    at /var/www/360lm/tests/recce.spec.js:896:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:42.527Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b46ba-min-overlay-HTML-JS-symbols-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 896
                      }
                    },
                    {
                      "workerIndex": 1600,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:896:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 896
                        },
                        "snippet": "  894 |     const resp = await ctx.get(BASE_URL);\n  895 |     const src = await resp.text();\n> 896 |     expect(src).toContain('id=\"ardl-bg\"');\n      |                 ^\n  897 |     expect(src).toContain('id=\"inv-mini-bg\"');\n  898 |     expect(src).toContain('function openAdminRecce');\n  899 |     expect(src).toContain('function markOfficialFromUI');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 896
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"ardl-bg\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  894 |     const resp = await ctx.get(BASE_URL);\n  895 |     const src = await resp.text();\n> 896 |     expect(src).toContain('id=\"ardl-bg\"');\n      |                 ^\n  897 |     expect(src).toContain('id=\"inv-mini-bg\"');\n  898 |     expect(src).toContain('function openAdminRecce');\n  899 |     expect(src).toContain('function markOfficialFromUI');\n    at /var/www/360lm/tests/recce.spec.js:896:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:43.388Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-b46ba-min-overlay-HTML-JS-symbols-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 896
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-77f595afa56c90a6b5a1",
              "file": "recce.spec.js",
              "line": 892,
              "column": 3
            },
            {
              "title": "css classes for client status pill + deprecated badge present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1601,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:909:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 909
                        },
                        "snippet": "  907 |     const resp = await ctx.get(BASE_URL);\n  908 |     const src = await resp.text();\n> 909 |     expect(src).toContain('.csl-pill');\n      |                 ^\n  910 |     expect(src).toContain('.csl-pill.approved');\n  911 |     expect(src).toContain('.csl-pill.approved_with_comments');\n  912 |     expect(src).toContain('.csl-pill.resubmission_required');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 909
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  907 |     const resp = await ctx.get(BASE_URL);\n  908 |     const src = await resp.text();\n> 909 |     expect(src).toContain('.csl-pill');\n      |                 ^\n  910 |     expect(src).toContain('.csl-pill.approved');\n  911 |     expect(src).toContain('.csl-pill.approved_with_comments');\n  912 |     expect(src).toContain('.csl-pill.resubmission_required');\n    at /var/www/360lm/tests/recce.spec.js:909:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:44.324Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4049c-ll-deprecated-badge-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 909
                      }
                    },
                    {
                      "workerIndex": 1602,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:909:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 909
                        },
                        "snippet": "  907 |     const resp = await ctx.get(BASE_URL);\n  908 |     const src = await resp.text();\n> 909 |     expect(src).toContain('.csl-pill');\n      |                 ^\n  910 |     expect(src).toContain('.csl-pill.approved');\n  911 |     expect(src).toContain('.csl-pill.approved_with_comments');\n  912 |     expect(src).toContain('.csl-pill.resubmission_required');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 909
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\".csl-pill\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  907 |     const resp = await ctx.get(BASE_URL);\n  908 |     const src = await resp.text();\n> 909 |     expect(src).toContain('.csl-pill');\n      |                 ^\n  910 |     expect(src).toContain('.csl-pill.approved');\n  911 |     expect(src).toContain('.csl-pill.approved_with_comments');\n  912 |     expect(src).toContain('.csl-pill.resubmission_required');\n    at /var/www/360lm/tests/recce.spec.js:909:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:45.230Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-4049c-ll-deprecated-badge-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 909
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-5d399419907adfca9eb7",
              "file": "recce.spec.js",
              "line": 905,
              "column": 3
            },
            {
              "title": "en + hi translation keys for v16 surface present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1603,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 51,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:924:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 924
                        },
                        "snippet": "  922 |     const src = await resp.text();\n  923 |     // en\n> 924 |     expect(src).toContain(\"av_csl_pending: 'Pending'\");\n      |                 ^\n  925 |     expect(src).toContain(\"av_csl_approved: 'Approved'\");\n  926 |     expect(src).toContain(\"av_ardl_lock_banner:\");\n  927 |     expect(src).toContain(\"av_ardl_mark_off:\");"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 924
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  922 |     const src = await resp.text();\n  923 |     // en\n> 924 |     expect(src).toContain(\"av_csl_pending: 'Pending'\");\n      |                 ^\n  925 |     expect(src).toContain(\"av_csl_approved: 'Approved'\");\n  926 |     expect(src).toContain(\"av_ardl_lock_banner:\");\n  927 |     expect(src).toContain(\"av_ardl_mark_off:\");\n    at /var/www/360lm/tests/recce.spec.js:924:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:46.106Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-1a6d9-eys-for-v16-surface-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 924
                      }
                    },
                    {
                      "workerIndex": 1604,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:924:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 924
                        },
                        "snippet": "  922 |     const src = await resp.text();\n  923 |     // en\n> 924 |     expect(src).toContain(\"av_csl_pending: 'Pending'\");\n      |                 ^\n  925 |     expect(src).toContain(\"av_csl_approved: 'Approved'\");\n  926 |     expect(src).toContain(\"av_ardl_lock_banner:\");\n  927 |     expect(src).toContain(\"av_ardl_mark_off:\");"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 924
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"av_csl_pending: 'Pending'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  922 |     const src = await resp.text();\n  923 |     // en\n> 924 |     expect(src).toContain(\"av_csl_pending: 'Pending'\");\n      |                 ^\n  925 |     expect(src).toContain(\"av_csl_approved: 'Approved'\");\n  926 |     expect(src).toContain(\"av_ardl_lock_banner:\");\n  927 |     expect(src).toContain(\"av_ardl_mark_off:\");\n    at /var/www/360lm/tests/recce.spec.js:924:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:47.002Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-1a6d9-eys-for-v16-surface-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 924
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-90b6adaf2f75f98d4b55",
              "file": "recce.spec.js",
              "line": 919,
              "column": 3
            },
            {
              "title": "client_visible_recces_v exposes superseded_at + is_official_for_client",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1605,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 310,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:47.938Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-8e93eebbf48237fd4f96",
              "file": "recce.spec.js",
              "line": 937,
              "column": 3
            },
            {
              "title": "recce-client renders deprecated badge + dep-banner CSS",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1605,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 16,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:954:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 954
                        },
                        "snippet": "  952 |     const resp = await request.get('https://srv1111289.hstgr.cloud/recce-client/');\n  953 |     const src = await resp.text();\n> 954 |     expect(src).toMatch(/recce-client-v\\d+/);\n      |                 ^\n  955 |     expect(src).toContain('.status-pill.deprecated');\n  956 |     expect(src).toContain('.dep-banner');\n  957 |     expect(src).toContain('id=\"v-dep-banner\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 954
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  952 |     const resp = await request.get('https://srv1111289.hstgr.cloud/recce-client/');\n  953 |     const src = await resp.text();\n> 954 |     expect(src).toMatch(/recce-client-v\\d+/);\n      |                 ^\n  955 |     expect(src).toContain('.status-pill.deprecated');\n  956 |     expect(src).toContain('.dep-banner');\n  957 |     expect(src).toContain('id=\"v-dep-banner\"');\n    at /var/www/360lm/tests/recce.spec.js:954:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:48.304Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-c7116-ecated-badge-dep-banner-CSS-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 954
                      }
                    },
                    {
                      "workerIndex": 1606,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/recce.spec.js:954:17",
                        "location": {
                          "file": "/var/www/360lm/tests/recce.spec.js",
                          "column": 17,
                          "line": 954
                        },
                        "snippet": "  952 |     const resp = await request.get('https://srv1111289.hstgr.cloud/recce-client/');\n  953 |     const src = await resp.text();\n> 954 |     expect(src).toMatch(/recce-client-v\\d+/);\n      |                 ^\n  955 |     expect(src).toContain('.status-pill.deprecated');\n  956 |     expect(src).toContain('.dep-banner');\n  957 |     expect(src).toContain('id=\"v-dep-banner\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 17,
                            "line": 954
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/recce-client-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  952 |     const resp = await request.get('https://srv1111289.hstgr.cloud/recce-client/');\n  953 |     const src = await resp.text();\n> 954 |     expect(src).toMatch(/recce-client-v\\d+/);\n      |                 ^\n  955 |     expect(src).toContain('.status-pill.deprecated');\n  956 |     expect(src).toContain('.dep-banner');\n  957 |     expect(src).toContain('id=\"v-dep-banner\"');\n    at /var/www/360lm/tests/recce.spec.js:954:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:40:49.189Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-v16-c7116-ecated-badge-dep-banner-CSS-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/recce.spec.js",
                        "column": 17,
                        "line": 954
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-252f164e070541de5468",
              "file": "recce.spec.js",
              "line": 951,
              "column": 3
            }
          ]
        },
        {
          "title": "Branding Recce — clearData() scoped to owner",
          "file": "recce.spec.js",
          "line": 962,
          "column": 6,
          "specs": [
            {
              "title": "clearData deletes only current user's subs rows, leaves other owners' rows intact",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 600,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30116,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 35,
                            "line": 974
                          },
                          "message": "Error: page.evaluate: Target page, context or browser has been closed\n\n  972 |\n  973 |     // Seed two owners' rows directly into IndexedDB (RecceDB v4, 'subs' store)\n> 974 |     const seedResult = await page.evaluate(async () => {\n      |                                   ^\n  975 |       await initDB();\n  976 |       await dbPut('subs', { id: 'sub-mine-1', owner: 'harish', name: 'My store A', status: 'pending' });\n  977 |       await dbPut('subs', { id: 'sub-other-1', owner: 'someoneelse', name: 'Other store A', status: 'pending' });\n    at /var/www/360lm/tests/recce.spec.js:974:35"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:40:56.715Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-cle-f7a28-es-other-owners-rows-intact-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-cle-f7a28-es-other-owners-rows-intact-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 601,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30135,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 35,
                            "line": 974
                          },
                          "message": "Error: page.evaluate: Target page, context or browser has been closed\n\n  972 |\n  973 |     // Seed two owners' rows directly into IndexedDB (RecceDB v4, 'subs' store)\n> 974 |     const seedResult = await page.evaluate(async () => {\n      |                                   ^\n  975 |       await initDB();\n  976 |       await dbPut('subs', { id: 'sub-mine-1', owner: 'harish', name: 'My store A', status: 'pending' });\n  977 |       await dbPut('subs', { id: 'sub-other-1', owner: 'someoneelse', name: 'Other store A', status: 'pending' });\n    at /var/www/360lm/tests/recce.spec.js:974:35"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:41:27.912Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-cle-f7a28-es-other-owners-rows-intact-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-cle-f7a28-es-other-owners-rows-intact-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-b568998e4ae27870f81b",
              "file": "recce.spec.js",
              "line": 968,
              "column": 3
            },
            {
              "title": "clearData deletes only current user's subs rows, leaves other owners' rows intact",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1607,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30123,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 35,
                            "line": 974
                          },
                          "message": "Error: page.evaluate: Target page, context or browser has been closed\n\n  972 |\n  973 |     // Seed two owners' rows directly into IndexedDB (RecceDB v4, 'subs' store)\n> 974 |     const seedResult = await page.evaluate(async () => {\n      |                                   ^\n  975 |       await initDB();\n  976 |       await dbPut('subs', { id: 'sub-mine-1', owner: 'harish', name: 'My store A', status: 'pending' });\n  977 |       await dbPut('subs', { id: 'sub-other-1', owner: 'someoneelse', name: 'Other store A', status: 'pending' });\n    at /var/www/360lm/tests/recce.spec.js:974:35"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:40:50.166Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-cle-f7a28-es-other-owners-rows-intact-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-cle-f7a28-es-other-owners-rows-intact-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1608,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30116,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/recce.spec.js",
                            "column": 35,
                            "line": 974
                          },
                          "message": "Error: page.evaluate: Target page, context or browser has been closed\n\n  972 |\n  973 |     // Seed two owners' rows directly into IndexedDB (RecceDB v4, 'subs' store)\n> 974 |     const seedResult = await page.evaluate(async () => {\n      |                                   ^\n  975 |       await initDB();\n  976 |       await dbPut('subs', { id: 'sub-mine-1', owner: 'harish', name: 'My store A', status: 'pending' });\n  977 |       await dbPut('subs', { id: 'sub-other-1', owner: 'someoneelse', name: 'Other store A', status: 'pending' });\n    at /var/www/360lm/tests/recce.spec.js:974:35"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:41:21.322Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-cle-f7a28-es-other-owners-rows-intact-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/recce-Branding-Recce-—-cle-f7a28-es-other-owners-rows-intact-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "bb27ca683ce0a15e9d22-2b23a14c5ecf57839369",
              "file": "recce.spec.js",
              "line": 968,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "rentveh-drafts.spec.js",
      "file": "rentveh-drafts.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Rent Vehicle — draft sync + print (ADR-139)",
          "file": "rentveh-drafts.spec.js",
          "line": 109,
          "column": 6,
          "specs": [
            {
              "title": "Mukesh can sign in with his real PIN and reach the PWA",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4362,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:41:59.111Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-e4508d7b79c0734a60af",
              "file": "rentveh-drafts.spec.js",
              "line": 119,
              "column": 3
            },
            {
              "title": "B1: a half-filled draft reaches the server (it used to stay local)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1844,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:03.867Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-b6ae7ab8ccf8c5dea4d9",
              "file": "rentveh-drafts.spec.js",
              "line": 128,
              "column": 3
            },
            {
              "title": "B2: that draft is visible on a second device",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 700,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:05.743Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-becad34a9449076f2671",
              "file": "rentveh-drafts.spec.js",
              "line": 153,
              "column": 3
            },
            {
              "title": "A: printing a not-yet-synced entry renders the photos held only on the device",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1334,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:06.481Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-c5afad11e34582d25239",
              "file": "rentveh-drafts.spec.js",
              "line": 168,
              "column": 3
            },
            {
              "title": "D: editing a draft pulled from the server keeps its toll amount and photo",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1848,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:07.829Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-bc578c7a1b109a8e7711",
              "file": "rentveh-drafts.spec.js",
              "line": 205,
              "column": 3
            },
            {
              "title": "C: deleting a synced draft removes it server-side, so it cannot come back",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4246,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:09.696Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-4a22f5e90687056cd97e",
              "file": "rentveh-drafts.spec.js",
              "line": 246,
              "column": 3
            },
            {
              "title": "Mukesh can sign in with his real PIN and reach the PWA",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2860,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:41:52.469Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-cae6ca25a7a16bf0ab49",
              "file": "rentveh-drafts.spec.js",
              "line": 119,
              "column": 3
            },
            {
              "title": "B1: a half-filled draft reaches the server (it used to stay local)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1093,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:41:55.622Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-17b8bf4ce62528df00f5",
              "file": "rentveh-drafts.spec.js",
              "line": 128,
              "column": 3
            },
            {
              "title": "B2: that draft is visible on a second device",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 576,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:41:56.743Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-b14a8be7a908c7d0d57f",
              "file": "rentveh-drafts.spec.js",
              "line": 153,
              "column": 3
            },
            {
              "title": "A: printing a not-yet-synced entry renders the photos held only on the device",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1185,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:41:57.336Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-8475f0556ab9f3861fc8",
              "file": "rentveh-drafts.spec.js",
              "line": 168,
              "column": 3
            },
            {
              "title": "D: editing a draft pulled from the server keeps its toll amount and photo",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1931,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:41:58.535Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-5861315d6483d2f00333",
              "file": "rentveh-drafts.spec.js",
              "line": 205,
              "column": 3
            },
            {
              "title": "C: deleting a synced draft removes it server-side, so it cannot come back",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4619,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:00.480Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e68548f3bb6d578696fb-7f09ffc1302cdaf69299",
              "file": "rentveh-drafts.spec.js",
              "line": 246,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "rentveh-parity.spec.js",
      "file": "rentveh-parity.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "RentVeh — team drafts + job/tour parity (ADR-140)",
          "file": "rentveh-parity.spec.js",
          "line": 48,
          "column": 6,
          "specs": [
            {
              "title": "TM sees a team member's DRAFT under the Team tab",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 527,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:14.023Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "ac221f71f46121f6d0a5-e5e5c9ed2d435e2024d7",
              "file": "rentveh-parity.spec.js",
              "line": 59,
              "column": 3
            },
            {
              "title": "the owner still sees their own draft in My mode",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 460,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:14.613Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "ac221f71f46121f6d0a5-09ee873e039cf1f7e67c",
              "file": "rentveh-parity.spec.js",
              "line": 78,
              "column": 3
            },
            {
              "title": "a non-approver is offered a REQUEST, not a create",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 662,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:15.094Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "ac221f71f46121f6d0a5-1e2dc7ae8aaa234afee1",
              "file": "rentveh-parity.spec.js",
              "line": 85,
              "column": 3
            },
            {
              "title": "an approver is offered a direct CREATE",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 751,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:15.770Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "ac221f71f46121f6d0a5-5bd3c36e775842255042",
              "file": "rentveh-parity.spec.js",
              "line": 96,
              "column": 3
            },
            {
              "title": "approver creates a job+tour in ONE step and it attaches to the expense",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1017,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:16.536Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "ac221f71f46121f6d0a5-75737e390c9351e9b69f",
              "file": "rentveh-parity.spec.js",
              "line": 108,
              "column": 3
            },
            {
              "title": "TM sees a team member's DRAFT under the Team tab",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 572,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:05.184Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "ac221f71f46121f6d0a5-d82b7e60d386c2663e3c",
              "file": "rentveh-parity.spec.js",
              "line": 59,
              "column": 3
            },
            {
              "title": "the owner still sees their own draft in My mode",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 520,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:05.810Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "ac221f71f46121f6d0a5-fc01398964a651d77fbf",
              "file": "rentveh-parity.spec.js",
              "line": 78,
              "column": 3
            },
            {
              "title": "a non-approver is offered a REQUEST, not a create",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 686,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:06.343Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "ac221f71f46121f6d0a5-6d38403ac1524ca5aada",
              "file": "rentveh-parity.spec.js",
              "line": 85,
              "column": 3
            },
            {
              "title": "an approver is offered a direct CREATE",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 718,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:07.049Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "ac221f71f46121f6d0a5-8b7534ff13a2e376a718",
              "file": "rentveh-parity.spec.js",
              "line": 96,
              "column": 3
            },
            {
              "title": "approver creates a job+tour in ONE step and it attaches to the expense",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1044,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:07.784Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "ac221f71f46121f6d0a5-6cb6e3e678271afe5805",
              "file": "rentveh-parity.spec.js",
              "line": 108,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "rentveh-terrain.spec.js",
      "file": "rentveh-terrain.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Rent Vehicle v5 — terrain rates",
          "file": "rentveh-terrain.spec.js",
          "line": 53,
          "column": 6,
          "specs": [
            {
              "title": "migration: existing masters keep their rates as PLAIN, hilly starts NULL",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 25,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:17.621Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-692f184f69168aa7ed6c",
              "file": "rentveh-terrain.spec.js",
              "line": 68,
              "column": 3
            },
            {
              "title": "master form round-trips the hilly pair; blank stays NULL, never 0",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 851,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:17.695Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-80b99e02b2cd5bad450b",
              "file": "rentveh-terrain.spec.js",
              "line": 80,
              "column": 3
            },
            {
              "title": "vehicle lists show both rate pairs, and name the ones with no hilly rate",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 631,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:18.566Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-7f8c09fbb03550355983",
              "file": "rentveh-terrain.spec.js",
              "line": 106,
              "column": 3
            },
            {
              "title": "switching to Hilly re-prices rent AND mileage from the master, and recalcs",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 800,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:19.218Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-6c22580ec3d32b445724",
              "file": "rentveh-terrain.spec.js",
              "line": 114,
              "column": 3
            },
            {
              "title": "the terrain switch works for a NON-TM, whose rate boxes stay read-only",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 656,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:20.035Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-5eae75e9e3371f948748",
              "file": "rentveh-terrain.spec.js",
              "line": 143,
              "column": 3
            },
            {
              "title": "a vehicle with no hilly rate degrades LOUDLY and blocks a non-TM submit",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 825,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:20.704Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-c6791200b613b11c8854",
              "file": "rentveh-terrain.spec.js",
              "line": 155,
              "column": 3
            },
            {
              "title": "draft snapshots terrain + the hilly rates to the server, and reopens on them",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1437,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:21.542Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-9e26719ae846602d8e1e",
              "file": "rentveh-terrain.spec.js",
              "line": 170,
              "column": 3
            },
            {
              "title": "migration: existing masters keep their rates as PLAIN, hilly starts NULL",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 26,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:08.907Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-89d33c5d992e4895fcbd",
              "file": "rentveh-terrain.spec.js",
              "line": 68,
              "column": 3
            },
            {
              "title": "master form round-trips the hilly pair; blank stays NULL, never 0",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 804,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:08.994Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-b4235ecc2077c88eeaae",
              "file": "rentveh-terrain.spec.js",
              "line": 80,
              "column": 3
            },
            {
              "title": "vehicle lists show both rate pairs, and name the ones with no hilly rate",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1609,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5462,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.pick-row:has-text(\"TEST-RV-TERR-A\")')\nExpected substring: \u001b[32m\"₹3,000/day\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.pick-row:has-text(\"TEST-RV-TERR-A\")')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.pick-row:has-text(\"TEST-RV-TERR-A\")')\nExpected substring: \u001b[32m\"₹3,000/day\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.pick-row:has-text(\"TEST-RV-TERR-A\")')\u001b[22m\n\n    at /var/www/360lm/tests/rentveh-terrain.spec.js:110:69",
                        "location": {
                          "file": "/var/www/360lm/tests/rentveh-terrain.spec.js",
                          "column": 69,
                          "line": 110
                        },
                        "snippet": "  108 |     await page.goto(BASE);\n  109 |     await page.click('#btn-settings');\n> 110 |     await expect(page.locator(`.pick-row:has-text(\"${VEH_BOTH}\")`)).toContainText('₹3,000/day');\n      |                                                                     ^\n  111 |     await expect(page.locator(`.pick-row:has-text(\"${VEH_PLAIN}\")`)).toContainText('hilly rate not set');\n  112 |   });\n  113 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh-terrain.spec.js",
                            "column": 69,
                            "line": 110
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.pick-row:has-text(\"TEST-RV-TERR-A\")')\nExpected substring: \u001b[32m\"₹3,000/day\"\u001b[39m\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.pick-row:has-text(\"TEST-RV-TERR-A\")')\u001b[22m\n\n\n  108 |     await page.goto(BASE);\n  109 |     await page.click('#btn-settings');\n> 110 |     await expect(page.locator(`.pick-row:has-text(\"${VEH_BOTH}\")`)).toContainText('₹3,000/day');\n      |                                                                     ^\n  111 |     await expect(page.locator(`.pick-row:has-text(\"${VEH_PLAIN}\")`)).toContainText('hilly rate not set');\n  112 |   });\n  113 |\n    at /var/www/360lm/tests/rentveh-terrain.spec.js:110:69"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:09.813Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-terrain-Rent-Vehic-b7c69-the-ones-with-no-hilly-rate-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-terrain-Rent-Vehic-b7c69-the-ones-with-no-hilly-rate-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-terrain-Rent-Vehic-b7c69-the-ones-with-no-hilly-rate-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/rentveh-terrain.spec.js",
                        "column": 69,
                        "line": 110
                      }
                    },
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 922,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:42:16.152Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "flaky"
                }
              ],
              "id": "12487c5b6ca59789e2bf-ce5f71bd1eb7aa1d755f",
              "file": "rentveh-terrain.spec.js",
              "line": 106,
              "column": 3
            },
            {
              "title": "switching to Hilly re-prices rent AND mileage from the master, and recalcs",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1091,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:17.344Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-ed1f4372de4e806d3cf0",
              "file": "rentveh-terrain.spec.js",
              "line": 114,
              "column": 3
            },
            {
              "title": "the terrain switch works for a NON-TM, whose rate boxes stay read-only",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 965,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:18.473Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-018c1c659e48656f2fbf",
              "file": "rentveh-terrain.spec.js",
              "line": 143,
              "column": 3
            },
            {
              "title": "a vehicle with no hilly rate degrades LOUDLY and blocks a non-TM submit",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 979,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:19.456Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-bf1e5470a98e468dc360",
              "file": "rentveh-terrain.spec.js",
              "line": 155,
              "column": 3
            },
            {
              "title": "draft snapshots terrain + the hilly rates to the server, and reopens on them",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1445,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:20.458Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-fff8de99e447750ae1e4",
              "file": "rentveh-terrain.spec.js",
              "line": 170,
              "column": 3
            }
          ]
        },
        {
          "title": "Rent Vehicle v5 — printable photo size",
          "file": "rentveh-terrain.spec.js",
          "line": 221,
          "column": 6,
          "specs": [
            {
              "title": "four size steps; Full width goes to ONE column and a far bigger box",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 776,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:23.051Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-6b7feb5d23dae10467ee",
              "file": "rentveh-terrain.spec.js",
              "line": 266,
              "column": 3
            },
            {
              "title": "the chosen size survives into PRINT media (the bug the old CSS had)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1024,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:23.931Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-a49fcd6c3e369be50754",
              "file": "rentveh-terrain.spec.js",
              "line": 285,
              "column": 3
            },
            {
              "title": "rotation still composes with every size step",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1072,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:24.972Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-f2da20cf8256dca27f31",
              "file": "rentveh-terrain.spec.js",
              "line": 314,
              "column": 3
            },
            {
              "title": "the size choice is remembered for the next print",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1167,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:26.058Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-0a2915bb2ebd93af7e28",
              "file": "rentveh-terrain.spec.js",
              "line": 332,
              "column": 3
            },
            {
              "title": "the print sheet names the terrain the expense was billed at",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 692,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:27.246Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-36d3bfc33709b8140643",
              "file": "rentveh-terrain.spec.js",
              "line": 345,
              "column": 3
            },
            {
              "title": "four size steps; Full width goes to ONE column and a far bigger box",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1076,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:21.977Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-3f222a4f076da5247a97",
              "file": "rentveh-terrain.spec.js",
              "line": 266,
              "column": 3
            },
            {
              "title": "the chosen size survives into PRINT media (the bug the old CSS had)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1332,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:23.145Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-4dd26f033dd240cc5ead",
              "file": "rentveh-terrain.spec.js",
              "line": 285,
              "column": 3
            },
            {
              "title": "rotation still composes with every size step",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1398,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:24.497Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-ff30615041aa3f6cc25e",
              "file": "rentveh-terrain.spec.js",
              "line": 314,
              "column": 3
            },
            {
              "title": "the size choice is remembered for the next print",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1577,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:25.909Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-d93f6c4caa06ceb5f1f9",
              "file": "rentveh-terrain.spec.js",
              "line": 332,
              "column": 3
            },
            {
              "title": "the print sheet names the terrain the expense was billed at",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 739,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:27.503Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "12487c5b6ca59789e2bf-a3783aa852892c68fef5",
              "file": "rentveh-terrain.spec.js",
              "line": 345,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "rentveh-v6.spec.js",
      "file": "rentveh-v6.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "ADR-143 — one Client & Brand master",
          "file": "rentveh-v6.spec.js",
          "line": 78,
          "column": 6,
          "specs": [
            {
              "title": "master.brands is live and recce.brands is the SAME list through a view",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 29,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:28.028Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-98301865ac40179a8f34",
              "file": "rentveh-v6.spec.js",
              "line": 79,
              "column": 3
            },
            {
              "title": "master.clients accepts a new client and derives its code",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 25,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:28.075Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-646efcf5f1b934179f88",
              "file": "rentveh-v6.spec.js",
              "line": 98,
              "column": 3
            },
            {
              "title": "master.brands is live and recce.brands is the SAME list through a view",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 28,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:28.330Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-27c4d6e7eef4f630970e",
              "file": "rentveh-v6.spec.js",
              "line": 79,
              "column": 3
            },
            {
              "title": "master.clients accepts a new client and derives its code",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 19,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:28.371Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-0acb85ab584d39d2029f",
              "file": "rentveh-v6.spec.js",
              "line": 98,
              "column": 3
            }
          ]
        },
        {
          "title": "Rent Vehicle v6 — form",
          "file": "rentveh-v6.spec.js",
          "line": 110,
          "column": 6,
          "specs": [
            {
              "title": "SW cache version matches CACHE_VER",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:28.115Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-1a9671ba930d0da6c629",
              "file": "rentveh-v6.spec.js",
              "line": 113,
              "column": 3
            },
            {
              "title": "purpose is replaced by Client & Brand + Destinations, and advances are multi-entry",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1734,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:28.142Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-0afd0a358c2feb1e6079",
              "file": "rentveh-v6.spec.js",
              "line": 118,
              "column": 3
            },
            {
              "title": "the tour-request dialog opens, prefills from the chips, and offers the master lists",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4402,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:29.891Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-a1b719a091da41775fc3",
              "file": "rentveh-v6.spec.js",
              "line": 140,
              "column": 3
            },
            {
              "title": "night charges add to the gross; advances and refuels deduct",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1357,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:34.307Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-b459eb36ce625af7504e",
              "file": "rentveh-v6.spec.js",
              "line": 169,
              "column": 3
            },
            {
              "title": "a night charge with no description cannot be submitted",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3348,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:35.680Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-91cfe3999773860e04fe",
              "file": "rentveh-v6.spec.js",
              "line": 192,
              "column": 3
            },
            {
              "title": "SW cache version matches CACHE_VER",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:28.401Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-72eca06204dc68763f67",
              "file": "rentveh-v6.spec.js",
              "line": 113,
              "column": 3
            },
            {
              "title": "purpose is replaced by Client & Brand + Destinations, and advances are multi-entry",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1766,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:28.423Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-ae465b2e77c36533deb9",
              "file": "rentveh-v6.spec.js",
              "line": 118,
              "column": 3
            },
            {
              "title": "the tour-request dialog opens, prefills from the chips, and offers the master lists",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4366,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:30.203Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-3e58363f16666a72b242",
              "file": "rentveh-v6.spec.js",
              "line": 140,
              "column": 3
            },
            {
              "title": "night charges add to the gross; advances and refuels deduct",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1637,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:34.588Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-af7fc842b6c4439a9e5f",
              "file": "rentveh-v6.spec.js",
              "line": 169,
              "column": 3
            },
            {
              "title": "a night charge with no description cannot be submitted",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3645,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:36.240Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-310b56b1ce44cf96a282",
              "file": "rentveh-v6.spec.js",
              "line": 192,
              "column": 3
            }
          ]
        },
        {
          "title": "Rent Vehicle v6 — round trip, approval and print",
          "file": "rentveh-v6.spec.js",
          "line": 220,
          "column": 6,
          "specs": [
            {
              "title": "every head lands on the server with its description",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6486,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:39.086Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-103b5bfc4bbb2508bf17",
              "file": "rentveh-v6.spec.js",
              "line": 223,
              "column": 3
            },
            {
              "title": "TM revises the night charge: the claim survives, both figures recorded, totals move",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14032,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:45.588Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-1dd8a5b60aac9d7529ce",
              "file": "rentveh-v6.spec.js",
              "line": 256,
              "column": 3
            },
            {
              "title": "print sheet carries Tour ID + every new field and still fits ONE A4 page",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 9217,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:42:59.639Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-497017c36d4772c06f91",
              "file": "rentveh-v6.spec.js",
              "line": 295,
              "column": 3
            },
            {
              "title": "a pre-v6 expense keeps its advance and its purpose",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5044,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:08.880Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-e0c5661dfa913ce8fb01",
              "file": "rentveh-v6.spec.js",
              "line": 331,
              "column": 3
            },
            {
              "title": "every head lands on the server with its description",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6807,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:39.943Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-66285643d828894d1521",
              "file": "rentveh-v6.spec.js",
              "line": 223,
              "column": 3
            },
            {
              "title": "TM revises the night charge: the claim survives, both figures recorded, totals move",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13997,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:42:46.765Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-8dd1b55aed37f4d8882f",
              "file": "rentveh-v6.spec.js",
              "line": 256,
              "column": 3
            },
            {
              "title": "print sheet carries Tour ID + every new field and still fits ONE A4 page",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 9192,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:00.781Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-e698d8c1e9bd21754b4c",
              "file": "rentveh-v6.spec.js",
              "line": 295,
              "column": 3
            },
            {
              "title": "a pre-v6 expense keeps its advance and its purpose",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 5061,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:09.996Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-6e7d459fa7baac19311e",
              "file": "rentveh-v6.spec.js",
              "line": 331,
              "column": 3
            }
          ]
        },
        {
          "title": "v6 regression — chip text typed without ＋ Add is not lost",
          "file": "rentveh-v6.spec.js",
          "line": 397,
          "column": 6,
          "specs": [
            {
              "title": "typed Client & Brand + Destinations reach the SERVER and the print sheet",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 7886,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:13.985Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-99cb3c8c629fdae363c5",
              "file": "rentveh-v6.spec.js",
              "line": 400,
              "column": 3
            },
            {
              "title": "typed Client & Brand + Destinations reach the SERVER and the print sheet",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 7994,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:15.120Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "41c52433cc32fb7fae64-518ce4fc7273a8241fac",
              "file": "rentveh-v6.spec.js",
              "line": 400,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "rentveh.spec.js",
      "file": "rentveh.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Rent Vehicle — registration & shell",
          "file": "rentveh.spec.js",
          "line": 31,
          "column": 6,
          "specs": [
            {
              "title": "registered in hub.pwa_registry (ADR-016)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:21.926Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-dd32305c2d1ee847545f",
              "file": "rentveh.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "SW cache version matches CACHE_VER in app",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:21.950Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-21389ab545418da2cf9e",
              "file": "rentveh.spec.js",
              "line": 41,
              "column": 3
            },
            {
              "title": "image attachment offers Camera AND Gallery AND ImgBind via the shared module",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 8563,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:21.976Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-093b7454132125384aa0",
              "file": "rentveh.spec.js",
              "line": 56,
              "column": 3
            },
            {
              "title": "no session → redirects to hub with ?next=",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 642,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:30.554Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-244374cfbaeffa9fda8d",
              "file": "rentveh.spec.js",
              "line": 110,
              "column": 3
            },
            {
              "title": "with session → home renders; regular user has no TM chrome",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 438,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:31.214Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-61d90481869d72411d9d",
              "file": "rentveh.spec.js",
              "line": 116,
              "column": 3
            },
            {
              "title": "TM gets dual-mode segment + settings gear",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 491,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:31.678Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-81344888e84c89b671c4",
              "file": "rentveh.spec.js",
              "line": 125,
              "column": 3
            },
            {
              "title": "registered in hub.pwa_registry (ADR-016)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:23.173Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-629efa131051dbac6720",
              "file": "rentveh.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "SW cache version matches CACHE_VER in app",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:23.194Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-fa90aa5a76440fc1d59c",
              "file": "rentveh.spec.js",
              "line": 41,
              "column": 3
            },
            {
              "title": "image attachment offers Camera AND Gallery AND ImgBind via the shared module",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 8472,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:23.214Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-6f87a6a57a4620f06ae6",
              "file": "rentveh.spec.js",
              "line": 56,
              "column": 3
            },
            {
              "title": "no session → redirects to hub with ?next=",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 600,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:31.700Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-7bacdb57c452b4f0ab80",
              "file": "rentveh.spec.js",
              "line": 110,
              "column": 3
            },
            {
              "title": "with session → home renders; regular user has no TM chrome",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 491,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:32.314Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-1b173c58aaf0c71cd499",
              "file": "rentveh.spec.js",
              "line": 116,
              "column": 3
            },
            {
              "title": "TM gets dual-mode segment + settings gear",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 560,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:32.821Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-314b8c56402d56ad989f",
              "file": "rentveh.spec.js",
              "line": 125,
              "column": 3
            }
          ]
        },
        {
          "title": "Rent Vehicle — tutorials (ADR-064/065)",
          "file": "rentveh.spec.js",
          "line": 135,
          "column": 6,
          "specs": [
            {
              "title": "screencast page serves with both language MP4s + view beacon",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 16,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:32.194Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-4b85182ea549149efab3",
              "file": "rentveh.spec.js",
              "line": 136,
              "column": 3
            },
            {
              "title": "scene guide JSON valid + every screenshot resolves",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 118,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:32.226Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-6979133bb9cf5874c90c",
              "file": "rentveh.spec.js",
              "line": 148,
              "column": 3
            },
            {
              "title": "both tutorials registered in learn catalog",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 8,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:32.360Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-60c345a7606a80dfa57c",
              "file": "rentveh.spec.js",
              "line": 162,
              "column": 3
            },
            {
              "title": "screencast page serves with both language MP4s + view beacon",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:33.395Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-3fea6cd30d9915cb6cb2",
              "file": "rentveh.spec.js",
              "line": 136,
              "column": 3
            },
            {
              "title": "scene guide JSON valid + every screenshot resolves",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 81,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:33.421Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-9d0bac8614ac81d98309",
              "file": "rentveh.spec.js",
              "line": 148,
              "column": 3
            },
            {
              "title": "both tutorials registered in learn catalog",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 9,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:33.514Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-fc37e2f8b2befcecaed4",
              "file": "rentveh.spec.js",
              "line": 162,
              "column": 3
            }
          ]
        },
        {
          "title": "Rent Vehicle — payment handoff to Fund Custodian",
          "file": "rentveh.spec.js",
          "line": 174,
          "column": 6,
          "specs": [
            {
              "title": "regular user (not TM/custodian) sees no Record Payment button",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 602,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30150,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 16,
                            "line": 179
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.exp-card:has(.b-approved)')\u001b[22m\n\n\n  177 |     await page.goto(BASE);\n  178 |     await page.waitForTimeout(2500);\n> 179 |     await page.click('.exp-card:has(.b-approved)');\n      |                ^\n  180 |     await expect(page.locator('#detail-ovl')).toHaveClass(/open/);\n  181 |     await expect(page.locator('#btn-record-pay')).toHaveCount(0);\n  182 |   });\n    at /var/www/360lm/tests/rentveh.spec.js:179:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:43:32.376Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-014c7-es-no-Record-Payment-button-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-014c7-es-no-Record-Payment-button-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 603,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30106,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 16,
                            "line": 179
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.exp-card:has(.b-approved)')\u001b[22m\n\n\n  177 |     await page.goto(BASE);\n  178 |     await page.waitForTimeout(2500);\n> 179 |     await page.click('.exp-card:has(.b-approved)');\n      |                ^\n  180 |     await expect(page.locator('#detail-ovl')).toHaveClass(/open/);\n  181 |     await expect(page.locator('#btn-record-pay')).toHaveCount(0);\n  182 |   });\n    at /var/www/360lm/tests/rentveh.spec.js:179:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:44:03.820Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-014c7-es-no-Record-Payment-button-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-014c7-es-no-Record-Payment-button-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-1e4a1d82fc5b24fb6333",
              "file": "rentveh.spec.js",
              "line": 175,
              "column": 3
            },
            {
              "title": "TM/custodian: Record Payment → custodian transfer prefilled (party, amount, CTH, narration)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 604,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8234,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n\n    at /var/www/360lm/tests/rentveh.spec.js:192:23",
                        "location": {
                          "file": "/var/www/360lm/tests/rentveh.spec.js",
                          "column": 23,
                          "line": 192
                        },
                        "snippet": "  190 |     const btn = page.locator('#btn-record-pay');\n  191 |     await expect(btn).toBeVisible();\n> 192 |     await expect(btn).toContainText('₹6,849.17');\n      |                       ^\n  193 |     await btn.click();\n  194 |     await page.waitForURL(/\\/finance\\/custodian\\//);\n  195 |     // handoff consumed + form prefilled"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 23,
                            "line": 192
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n\n\n  190 |     const btn = page.locator('#btn-record-pay');\n  191 |     await expect(btn).toBeVisible();\n> 192 |     await expect(btn).toContainText('₹6,849.17');\n      |                       ^\n  193 |     await btn.click();\n  194 |     await page.waitForURL(/\\/finance\\/custodian\\//);\n  195 |     // handoff consumed + form prefilled\n    at /var/www/360lm/tests/rentveh.spec.js:192:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:44:34.954Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-81285-party-amount-CTH-narration--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-81285-party-amount-CTH-narration--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/rentveh.spec.js",
                        "column": 23,
                        "line": 192
                      }
                    },
                    {
                      "workerIndex": 605,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8151,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n\n    at /var/www/360lm/tests/rentveh.spec.js:192:23",
                        "location": {
                          "file": "/var/www/360lm/tests/rentveh.spec.js",
                          "column": 23,
                          "line": 192
                        },
                        "snippet": "  190 |     const btn = page.locator('#btn-record-pay');\n  191 |     await expect(btn).toBeVisible();\n> 192 |     await expect(btn).toContainText('₹6,849.17');\n      |                       ^\n  193 |     await btn.click();\n  194 |     await page.waitForURL(/\\/finance\\/custodian\\//);\n  195 |     // handoff consumed + form prefilled"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 23,
                            "line": 192
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n\n\n  190 |     const btn = page.locator('#btn-record-pay');\n  191 |     await expect(btn).toBeVisible();\n> 192 |     await expect(btn).toContainText('₹6,849.17');\n      |                       ^\n  193 |     await btn.click();\n  194 |     await page.waitForURL(/\\/finance\\/custodian\\//);\n  195 |     // handoff consumed + form prefilled\n    at /var/www/360lm/tests/rentveh.spec.js:192:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:44:44.090Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-81285-party-amount-CTH-narration--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-81285-party-amount-CTH-narration--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/rentveh.spec.js",
                        "column": 23,
                        "line": 192
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-96272164a948bd8e457d",
              "file": "rentveh.spec.js",
              "line": 184,
              "column": 3
            },
            {
              "title": "regular user (not TM/custodian) sees no Record Payment button",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1610,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30178,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 16,
                            "line": 179
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.exp-card:has(.b-approved)')\u001b[22m\n\n\n  177 |     await page.goto(BASE);\n  178 |     await page.waitForTimeout(2500);\n> 179 |     await page.click('.exp-card:has(.b-approved)');\n      |                ^\n  180 |     await expect(page.locator('#detail-ovl')).toHaveClass(/open/);\n  181 |     await expect(page.locator('#btn-record-pay')).toHaveCount(0);\n  182 |   });\n    at /var/www/360lm/tests/rentveh.spec.js:179:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:43:33.531Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-014c7-es-no-Record-Payment-button-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-014c7-es-no-Record-Payment-button-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1611,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30117,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 16,
                            "line": 179
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.exp-card:has(.b-approved)')\u001b[22m\n\n\n  177 |     await page.goto(BASE);\n  178 |     await page.waitForTimeout(2500);\n> 179 |     await page.click('.exp-card:has(.b-approved)');\n      |                ^\n  180 |     await expect(page.locator('#detail-ovl')).toHaveClass(/open/);\n  181 |     await expect(page.locator('#btn-record-pay')).toHaveCount(0);\n  182 |   });\n    at /var/www/360lm/tests/rentveh.spec.js:179:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:44:04.843Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-014c7-es-no-Record-Payment-button-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-014c7-es-no-Record-Payment-button-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-4eaca7dcb6b1b1358ab5",
              "file": "rentveh.spec.js",
              "line": 175,
              "column": 3
            },
            {
              "title": "TM/custodian: Record Payment → custodian transfer prefilled (party, amount, CTH, narration)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1612,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8223,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n\n    at /var/www/360lm/tests/rentveh.spec.js:192:23",
                        "location": {
                          "file": "/var/www/360lm/tests/rentveh.spec.js",
                          "column": 23,
                          "line": 192
                        },
                        "snippet": "  190 |     const btn = page.locator('#btn-record-pay');\n  191 |     await expect(btn).toBeVisible();\n> 192 |     await expect(btn).toContainText('₹6,849.17');\n      |                       ^\n  193 |     await btn.click();\n  194 |     await page.waitForURL(/\\/finance\\/custodian\\//);\n  195 |     // handoff consumed + form prefilled"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 23,
                            "line": 192
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n\n\n  190 |     const btn = page.locator('#btn-record-pay');\n  191 |     await expect(btn).toBeVisible();\n> 192 |     await expect(btn).toContainText('₹6,849.17');\n      |                       ^\n  193 |     await btn.click();\n  194 |     await page.waitForURL(/\\/finance\\/custodian\\//);\n  195 |     // handoff consumed + form prefilled\n    at /var/www/360lm/tests/rentveh.spec.js:192:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:44:35.983Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-81285-party-amount-CTH-narration--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-81285-party-amount-CTH-narration--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/rentveh.spec.js",
                        "column": 23,
                        "line": 192
                      }
                    },
                    {
                      "workerIndex": 1613,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8185,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n\n    at /var/www/360lm/tests/rentveh.spec.js:192:23",
                        "location": {
                          "file": "/var/www/360lm/tests/rentveh.spec.js",
                          "column": 23,
                          "line": 192
                        },
                        "snippet": "  190 |     const btn = page.locator('#btn-record-pay');\n  191 |     await expect(btn).toBeVisible();\n> 192 |     await expect(btn).toContainText('₹6,849.17');\n      |                       ^\n  193 |     await btn.click();\n  194 |     await page.waitForURL(/\\/finance\\/custodian\\//);\n  195 |     // handoff consumed + form prefilled"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 23,
                            "line": 192
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#btn-record-pay')\nExpected substring: \u001b[32m\"₹6,849.17\"\u001b[39m\nReceived string:    \u001b[31m\"💸 Record Payment — ₹19,714.30\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#btn-record-pay')\u001b[22m\n\u001b[2m    14 × locator resolved to <button class=\"btn-p\" id=\"btn-record-pay\" onclick=\"recordPayment('RV_1785485566783_0U65')\">💸 Record Payment — ₹19,714.30</button>\u001b[22m\n\u001b[2m       - unexpected value \"💸 Record Payment — ₹19,714.30\"\u001b[22m\n\n\n  190 |     const btn = page.locator('#btn-record-pay');\n  191 |     await expect(btn).toBeVisible();\n> 192 |     await expect(btn).toContainText('₹6,849.17');\n      |                       ^\n  193 |     await btn.click();\n  194 |     await page.waitForURL(/\\/finance\\/custodian\\//);\n  195 |     // handoff consumed + form prefilled\n    at /var/www/360lm/tests/rentveh.spec.js:192:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:44:45.190Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-81285-party-amount-CTH-narration--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-pay-81285-party-amount-CTH-narration--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/rentveh.spec.js",
                        "column": 23,
                        "line": 192
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-06afc0d23471ca2a2a43",
              "file": "rentveh.spec.js",
              "line": 184,
              "column": 3
            }
          ]
        },
        {
          "title": "Rent Vehicle — form & calculation",
          "file": "rentveh.spec.js",
          "line": 206,
          "column": 6,
          "specs": [
            {
              "title": "days rule: 26.5h with grace → 1 day + late-return flag",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 606,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 807,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:44:53.178Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-7742a47d2b380d2129a4",
              "file": "rentveh.spec.js",
              "line": 216,
              "column": 3
            },
            {
              "title": "full calculation: rent + fuel + toll + food − deductions = payable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 606,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 841,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:44:54.259Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-1fe51e818bca76c35214",
              "file": "rentveh.spec.js",
              "line": 233,
              "column": 3
            },
            {
              "title": "submit → row lands in DB with payable amount",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 606,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5765,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n\n    at /var/www/360lm/tests/rentveh.spec.js:272:53",
                        "location": {
                          "file": "/var/www/360lm/tests/rentveh.spec.js",
                          "column": 53,
                          "line": 272
                        },
                        "snippet": "  270 |     await page.fill('#f-fuel-rate', '100');\n  271 |     await page.click('button:has-text(\"Submit ✓\")');\n> 272 |     await expect(page.locator('.exp-card').first()).toBeVisible();\n      |                                                     ^\n  273 |     await expect(page.locator('.exp-card .badge.b-submitted').first()).toBeVisible();\n  274 |     const r = await request.get(\n  275 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 53,
                            "line": 272
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n\n\n  270 |     await page.fill('#f-fuel-rate', '100');\n  271 |     await page.click('button:has-text(\"Submit ✓\")');\n> 272 |     await expect(page.locator('.exp-card').first()).toBeVisible();\n      |                                                     ^\n  273 |     await expect(page.locator('.exp-card .badge.b-submitted').first()).toBeVisible();\n  274 |     const r = await request.get(\n  275 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });\n    at /var/www/360lm/tests/rentveh.spec.js:272:53"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:44:55.123Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/rentveh.spec.js",
                        "column": 53,
                        "line": 272
                      }
                    },
                    {
                      "workerIndex": 607,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6891,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n\n    at /var/www/360lm/tests/rentveh.spec.js:272:53",
                        "location": {
                          "file": "/var/www/360lm/tests/rentveh.spec.js",
                          "column": 53,
                          "line": 272
                        },
                        "snippet": "  270 |     await page.fill('#f-fuel-rate', '100');\n  271 |     await page.click('button:has-text(\"Submit ✓\")');\n> 272 |     await expect(page.locator('.exp-card').first()).toBeVisible();\n      |                                                     ^\n  273 |     await expect(page.locator('.exp-card .badge.b-submitted').first()).toBeVisible();\n  274 |     const r = await request.get(\n  275 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 53,
                            "line": 272
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n\n\n  270 |     await page.fill('#f-fuel-rate', '100');\n  271 |     await page.click('button:has-text(\"Submit ✓\")');\n> 272 |     await expect(page.locator('.exp-card').first()).toBeVisible();\n      |                                                     ^\n  273 |     await expect(page.locator('.exp-card .badge.b-submitted').first()).toBeVisible();\n  274 |     const r = await request.get(\n  275 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });\n    at /var/www/360lm/tests/rentveh.spec.js:272:53"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:45:02.844Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/rentveh.spec.js",
                        "column": 53,
                        "line": 272
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-50658b83c0d3260aec24",
              "file": "rentveh.spec.js",
              "line": 260,
              "column": 3
            },
            {
              "title": "TM approval flow: team mode → approve → status approved",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 608,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30135,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 16,
                            "line": 290
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('button:has-text(\"✓ Approve\")')\u001b[22m\n\u001b[2m    - locator resolved to 2 elements. Proceeding with the first one: <button class=\"btn-p\" id=\"appr-approve-btn\" onclick=\"confirmApprove()\">✓ Approve</button>\u001b[22m\n\u001b[2m  - attempting click action\u001b[22m\n\u001b[2m    2 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m      - element is not visible\u001b[22m\n\u001b[2m    - retrying click action\u001b[22m\n\u001b[2m    - waiting 20ms\u001b[22m\n\u001b[2m    2 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m      - element is not visible\u001b[22m\n\u001b[2m    - retrying click action\u001b[22m\n\u001b[2m      - waiting 100ms\u001b[22m\n\u001b[2m    57 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m       - element is not visible\u001b[22m\n\u001b[2m     - retrying click action\u001b[22m\n\u001b[2m       - waiting 500ms\u001b[22m\n\n\n  288 |     await page.click('.exp-card:has(.b-submitted)');\n  289 |     await expect(page.locator('#detail-ovl')).toHaveClass(/open/);\n> 290 |     await page.click('button:has-text(\"✓ Approve\")');\n      |                ^\n  291 |     await expect(page.locator('#detail-ovl')).not.toHaveClass(/open/);\n  292 |     const r = await request.get(\n  293 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });\n    at /var/www/360lm/tests/rentveh.spec.js:290:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:45:11.278Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 609,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30128,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 16,
                            "line": 290
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('button:has-text(\"✓ Approve\")')\u001b[22m\n\u001b[2m    - locator resolved to 2 elements. Proceeding with the first one: <button class=\"btn-p\" id=\"appr-approve-btn\" onclick=\"confirmApprove()\">✓ Approve</button>\u001b[22m\n\u001b[2m  - attempting click action\u001b[22m\n\u001b[2m    2 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m      - element is not visible\u001b[22m\n\u001b[2m    - retrying click action\u001b[22m\n\u001b[2m    - waiting 20ms\u001b[22m\n\u001b[2m    2 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m      - element is not visible\u001b[22m\n\u001b[2m    - retrying click action\u001b[22m\n\u001b[2m      - waiting 100ms\u001b[22m\n\u001b[2m    57 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m       - element is not visible\u001b[22m\n\u001b[2m     - retrying click action\u001b[22m\n\u001b[2m       - waiting 500ms\u001b[22m\n\n\n  288 |     await page.click('.exp-card:has(.b-submitted)');\n  289 |     await expect(page.locator('#detail-ovl')).toHaveClass(/open/);\n> 290 |     await page.click('button:has-text(\"✓ Approve\")');\n      |                ^\n  291 |     await expect(page.locator('#detail-ovl')).not.toHaveClass(/open/);\n  292 |     const r = await request.get(\n  293 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });\n    at /var/www/360lm/tests/rentveh.spec.js:290:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:45:42.540Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-8ad395752dfe140948af",
              "file": "rentveh.spec.js",
              "line": 284,
              "column": 3
            },
            {
              "title": "empty submit shows validation toast",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 610,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 699,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:13.877Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-e33d8808ead9c2917330",
              "file": "rentveh.spec.js",
              "line": 299,
              "column": 3
            },
            {
              "title": "days rule: 26.5h with grace → 1 day + late-return flag",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1614,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 923,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:44:54.401Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-86cf35297a38b518f224",
              "file": "rentveh.spec.js",
              "line": 216,
              "column": 3
            },
            {
              "title": "full calculation: rent + fuel + toll + food − deductions = payable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1614,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 962,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:44:55.594Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-6eb69b36af3b48587f11",
              "file": "rentveh.spec.js",
              "line": 233,
              "column": 3
            },
            {
              "title": "submit → row lands in DB with payable amount",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1614,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6145,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n\n    at /var/www/360lm/tests/rentveh.spec.js:272:53",
                        "location": {
                          "file": "/var/www/360lm/tests/rentveh.spec.js",
                          "column": 53,
                          "line": 272
                        },
                        "snippet": "  270 |     await page.fill('#f-fuel-rate', '100');\n  271 |     await page.click('button:has-text(\"Submit ✓\")');\n> 272 |     await expect(page.locator('.exp-card').first()).toBeVisible();\n      |                                                     ^\n  273 |     await expect(page.locator('.exp-card .badge.b-submitted').first()).toBeVisible();\n  274 |     const r = await request.get(\n  275 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 53,
                            "line": 272
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n\n\n  270 |     await page.fill('#f-fuel-rate', '100');\n  271 |     await page.click('button:has-text(\"Submit ✓\")');\n> 272 |     await expect(page.locator('.exp-card').first()).toBeVisible();\n      |                                                     ^\n  273 |     await expect(page.locator('.exp-card .badge.b-submitted').first()).toBeVisible();\n  274 |     const r = await request.get(\n  275 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });\n    at /var/www/360lm/tests/rentveh.spec.js:272:53"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:44:56.591Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/rentveh.spec.js",
                        "column": 53,
                        "line": 272
                      }
                    },
                    {
                      "workerIndex": 1615,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6293,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n\n    at /var/www/360lm/tests/rentveh.spec.js:272:53",
                        "location": {
                          "file": "/var/www/360lm/tests/rentveh.spec.js",
                          "column": 53,
                          "line": 272
                        },
                        "snippet": "  270 |     await page.fill('#f-fuel-rate', '100');\n  271 |     await page.click('button:has-text(\"Submit ✓\")');\n> 272 |     await expect(page.locator('.exp-card').first()).toBeVisible();\n      |                                                     ^\n  273 |     await expect(page.locator('.exp-card .badge.b-submitted').first()).toBeVisible();\n  274 |     const r = await request.get(\n  275 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 53,
                            "line": 272
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.exp-card').first()\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('.exp-card').first()\u001b[22m\n\n\n  270 |     await page.fill('#f-fuel-rate', '100');\n  271 |     await page.click('button:has-text(\"Submit ✓\")');\n> 272 |     await expect(page.locator('.exp-card').first()).toBeVisible();\n      |                                                     ^\n  273 |     await expect(page.locator('.exp-card .badge.b-submitted').first()).toBeVisible();\n  274 |     const r = await request.get(\n  275 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });\n    at /var/www/360lm/tests/rentveh.spec.js:272:53"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:45:04.734Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-24ed4-s-in-DB-with-payable-amount-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/rentveh.spec.js",
                        "column": 53,
                        "line": 272
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-49c356903b6295c6ea20",
              "file": "rentveh.spec.js",
              "line": 260,
              "column": 3
            },
            {
              "title": "TM approval flow: team mode → approve → status approved",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1616,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30148,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 16,
                            "line": 290
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('button:has-text(\"✓ Approve\")')\u001b[22m\n\u001b[2m    - locator resolved to 2 elements. Proceeding with the first one: <button class=\"btn-p\" id=\"appr-approve-btn\" onclick=\"confirmApprove()\">✓ Approve</button>\u001b[22m\n\u001b[2m  - attempting click action\u001b[22m\n\u001b[2m    2 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m      - element is not visible\u001b[22m\n\u001b[2m    - retrying click action\u001b[22m\n\u001b[2m    - waiting 20ms\u001b[22m\n\u001b[2m    2 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m      - element is not visible\u001b[22m\n\u001b[2m    - retrying click action\u001b[22m\n\u001b[2m      - waiting 100ms\u001b[22m\n\u001b[2m    57 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m       - element is not visible\u001b[22m\n\u001b[2m     - retrying click action\u001b[22m\n\u001b[2m       - waiting 500ms\u001b[22m\n\n\n  288 |     await page.click('.exp-card:has(.b-submitted)');\n  289 |     await expect(page.locator('#detail-ovl')).toHaveClass(/open/);\n> 290 |     await page.click('button:has-text(\"✓ Approve\")');\n      |                ^\n  291 |     await expect(page.locator('#detail-ovl')).not.toHaveClass(/open/);\n  292 |     const r = await request.get(\n  293 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });\n    at /var/www/360lm/tests/rentveh.spec.js:290:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:45:12.360Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1617,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30120,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/rentveh.spec.js",
                            "column": 16,
                            "line": 290
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('button:has-text(\"✓ Approve\")')\u001b[22m\n\u001b[2m    - locator resolved to 2 elements. Proceeding with the first one: <button class=\"btn-p\" id=\"appr-approve-btn\" onclick=\"confirmApprove()\">✓ Approve</button>\u001b[22m\n\u001b[2m  - attempting click action\u001b[22m\n\u001b[2m    2 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m      - element is not visible\u001b[22m\n\u001b[2m    - retrying click action\u001b[22m\n\u001b[2m    - waiting 20ms\u001b[22m\n\u001b[2m    2 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m      - element is not visible\u001b[22m\n\u001b[2m    - retrying click action\u001b[22m\n\u001b[2m      - waiting 100ms\u001b[22m\n\u001b[2m    57 × waiting for element to be visible, enabled and stable\u001b[22m\n\u001b[2m       - element is not visible\u001b[22m\n\u001b[2m     - retrying click action\u001b[22m\n\u001b[2m       - waiting 500ms\u001b[22m\n\n\n  288 |     await page.click('.exp-card:has(.b-submitted)');\n  289 |     await expect(page.locator('#detail-ovl')).toHaveClass(/open/);\n> 290 |     await page.click('button:has-text(\"✓ Approve\")');\n      |                ^\n  291 |     await expect(page.locator('#detail-ovl')).not.toHaveClass(/open/);\n  292 |     const r = await request.get(\n  293 |       `${DB}/expenses_list_v?emp_id=eq.sachin&order=created_at.desc&limit=1`, { headers: RV });\n    at /var/www/360lm/tests/rentveh.spec.js:290:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:45:43.635Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/rentveh-Rent-Vehicle-—-for-cd42a-→-approve-→-status-approved-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-1b045b1b6a8243aba1f1",
              "file": "rentveh.spec.js",
              "line": 284,
              "column": 3
            },
            {
              "title": "empty submit shows validation toast",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1618,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 673,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:14.859Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "61f16f2fd58ccb1dd4f3-ce2f1fcd6d336082ddeb",
              "file": "rentveh.spec.js",
              "line": 299,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "safe_bottom.spec.js",
      "file": "safe_bottom.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Safe-bottom — shared CSS file",
          "file": "safe_bottom.spec.js",
          "line": 51,
          "column": 6,
          "specs": [
            {
              "title": "/shared/safe-bottom.css returns 200 and contains :root rule",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 610,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 36,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:55:24",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 24,
                          "line": 55
                        },
                        "snippet": "  53 |     const ctx = await request.newContext();\n  54 |     const r = await ctx.get(`${BASE}/shared/safe-bottom.css`);\n> 55 |     expect(r.status()).toBe(200);\n     |                        ^\n  56 |     const css = await r.text();\n  57 |     expect(css).toContain('--safe-bottom-min');\n  58 |     expect(css).toContain('100px');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 24,
                            "line": 55
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  53 |     const ctx = await request.newContext();\n  54 |     const r = await ctx.get(`${BASE}/shared/safe-bottom.css`);\n> 55 |     expect(r.status()).toBe(200);\n     |                        ^\n  56 |     const css = await r.text();\n  57 |     expect(css).toContain('--safe-bottom-min');\n  58 |     expect(css).toContain('100px');\n    at /var/www/360lm/tests/safe_bottom.spec.js:55:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:14.980Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--7b05d--200-and-contains-root-rule-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 24,
                        "line": 55
                      }
                    },
                    {
                      "workerIndex": 611,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 50,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:55:24",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 24,
                          "line": 55
                        },
                        "snippet": "  53 |     const ctx = await request.newContext();\n  54 |     const r = await ctx.get(`${BASE}/shared/safe-bottom.css`);\n> 55 |     expect(r.status()).toBe(200);\n     |                        ^\n  56 |     const css = await r.text();\n  57 |     expect(css).toContain('--safe-bottom-min');\n  58 |     expect(css).toContain('100px');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 24,
                            "line": 55
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  53 |     const ctx = await request.newContext();\n  54 |     const r = await ctx.get(`${BASE}/shared/safe-bottom.css`);\n> 55 |     expect(r.status()).toBe(200);\n     |                        ^\n  56 |     const css = await r.text();\n  57 |     expect(css).toContain('--safe-bottom-min');\n  58 |     expect(css).toContain('100px');\n    at /var/www/360lm/tests/safe_bottom.spec.js:55:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:15.833Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--7b05d--200-and-contains-root-rule-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 24,
                        "line": 55
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-c757146b22ef428fed41",
              "file": "safe_bottom.spec.js",
              "line": 52,
              "column": 3
            },
            {
              "title": "/shared/safe-bottom.css returns 200 and contains :root rule",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1618,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 27,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:55:24",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 24,
                          "line": 55
                        },
                        "snippet": "  53 |     const ctx = await request.newContext();\n  54 |     const r = await ctx.get(`${BASE}/shared/safe-bottom.css`);\n> 55 |     expect(r.status()).toBe(200);\n     |                        ^\n  56 |     const css = await r.text();\n  57 |     expect(css).toContain('--safe-bottom-min');\n  58 |     expect(css).toContain('100px');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 24,
                            "line": 55
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  53 |     const ctx = await request.newContext();\n  54 |     const r = await ctx.get(`${BASE}/shared/safe-bottom.css`);\n> 55 |     expect(r.status()).toBe(200);\n     |                        ^\n  56 |     const css = await r.text();\n  57 |     expect(css).toContain('--safe-bottom-min');\n  58 |     expect(css).toContain('100px');\n    at /var/www/360lm/tests/safe_bottom.spec.js:55:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:15.897Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--7b05d--200-and-contains-root-rule-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 24,
                        "line": 55
                      }
                    },
                    {
                      "workerIndex": 1619,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:55:24",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 24,
                          "line": 55
                        },
                        "snippet": "  53 |     const ctx = await request.newContext();\n  54 |     const r = await ctx.get(`${BASE}/shared/safe-bottom.css`);\n> 55 |     expect(r.status()).toBe(200);\n     |                        ^\n  56 |     const css = await r.text();\n  57 |     expect(css).toContain('--safe-bottom-min');\n  58 |     expect(css).toContain('100px');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 24,
                            "line": 55
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  53 |     const ctx = await request.newContext();\n  54 |     const r = await ctx.get(`${BASE}/shared/safe-bottom.css`);\n> 55 |     expect(r.status()).toBe(200);\n     |                        ^\n  56 |     const css = await r.text();\n  57 |     expect(css).toContain('--safe-bottom-min');\n  58 |     expect(css).toContain('100px');\n    at /var/www/360lm/tests/safe_bottom.spec.js:55:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:16.743Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--7b05d--200-and-contains-root-rule-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 24,
                        "line": 55
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-1155c550366dc013522b",
              "file": "safe_bottom.spec.js",
              "line": 52,
              "column": 3
            }
          ]
        },
        {
          "title": "Safe-bottom — link tag",
          "file": "safe_bottom.spec.js",
          "line": 68,
          "column": 6,
          "specs": [
            {
              "title": "expense HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 612,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:16.716Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--6b8d7--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 613,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 71,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:17.599Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--6b8d7--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-e52ee4ea533deb68bebc",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "recce HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 614,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:18.505Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--d91e4--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 615,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:19.362Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--d91e4--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-0266bb7b0551678bf68f",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "installation HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 616,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:20.241Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ebf12--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 617,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:21.178Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ebf12--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-c779b69e540e2c5c977f",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "finance HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 618,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 51,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:22.029Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--31180--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 619,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:22.856Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--31180--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-08e6335f90e3c42eda24",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "finance-upi HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 620,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:23.747Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--1ad28--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 621,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:24.577Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--1ad28--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-ad2266e9e42f463d595a",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "finance-credit-card HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 622,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:25.417Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--43f7c--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 623,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:26.335Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--43f7c--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-f9381057a2ce7afe9860",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "finance-custodian HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 624,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:27.250Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ac164--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 625,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 50,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:28.128Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ac164--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-c9d9405d58047ef5c329",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "finance-vendors HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 626,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:29.016Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--5d228--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 627,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:29.987Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--5d228--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-db001e7f58eca78782e7",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "sales HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 628,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:30.888Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--8012c--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 629,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:31.754Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--8012c--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-24892a96508554aa3190",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "sales-customers HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 630,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 50,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:32.623Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ea3fc--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 631,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:33.438Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ea3fc--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-c816349436cf3cfbdc79",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "stores HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 632,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:34.311Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--64d94--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 633,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:35.202Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--64d94--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-33685a78608aece89242",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "hr HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 634,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 51,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:36.059Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--b28c2--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 635,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:36.937Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--b28c2--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-e64c2066ba27dd22b7fa",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "vehicle HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 636,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:37.831Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--42538--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 637,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:38.705Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--42538--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-a0c1cd101af5d34b6507",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "admin HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 638,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:39.563Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--10093--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 639,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 73,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:40.454Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--10093--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-97258186e8b128e2c679",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "activity HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 640,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:41.425Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--3b469--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 641,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:42.269Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--3b469--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-761f00f1d4ae1dab19de",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "production HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 642,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 50,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:43.104Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--d5363--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 643,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:43.952Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--d5363--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-0d33a11bce790aeeb556",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "tour-planner HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 644,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 48,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:44.783Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--3aeb1--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 645,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:45.669Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--3aeb1--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-be29a34ee16f8ca0c54e",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "tour-pg HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 646,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 51,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:46.499Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--7bdc5--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 647,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:47.343Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--7bdc5--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-a829968055fe7ce38754",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "hub HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 648,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:48.247Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--5e9a6--HTML-loads-safe-bottom-css-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 649,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:46:49.123Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--5e9a6--HTML-loads-safe-bottom-css-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-69daf62e8af71b0d1887",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "expense HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1620,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:17.657Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--6b8d7--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1621,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:18.544Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--6b8d7--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-0e8028e052d1d032e38c",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "recce HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1622,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:19.465Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--d91e4--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1623,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 67,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:20.364Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--d91e4--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-d424f817242893d7915f",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "installation HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1624,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:21.234Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ebf12--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1625,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:22.133Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ebf12--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-e582f16b064b94659a16",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "finance HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1626,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:23.016Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--31180--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1627,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 48,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:23.879Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--31180--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-6736c0a64af9a4d1a3a0",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "finance-upi HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1628,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:24.701Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--1ad28--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1629,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:25.584Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--1ad28--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-36794abdd394af79e0d5",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "finance-credit-card HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1630,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 68,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:26.503Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--43f7c--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1631,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:27.459Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--43f7c--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-f8e2a95734afffaf1b32",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "finance-custodian HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1632,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:28.331Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ac164--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1633,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:29.257Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ac164--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-df92b5db564d5c796428",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "finance-vendors HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1634,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:30.123Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--5d228--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1635,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:30.971Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--5d228--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-94819400730e79c2a7dd",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "sales HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1636,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:31.840Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--8012c--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1637,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:32.710Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--8012c--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-be013bd0c51394eec3fc",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "sales-customers HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1638,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:33.592Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ea3fc--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1639,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:34.452Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--ea3fc--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-1ab8cf08fdea67a55cd1",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "stores HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1640,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 49,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:35.270Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--64d94--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1641,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 51,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:36.137Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--64d94--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-6cae1418d0ce75b0ef10",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "hr HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1642,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 75,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:36.974Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--b28c2--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1643,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:37.888Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--b28c2--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-45b47e65c31a1f053c3a",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "vehicle HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1644,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 51,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:38.876Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--42538--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1645,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 67,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:39.784Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--42538--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-121634ff7e51c450b736",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "admin HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1646,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:40.734Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--10093--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1647,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 50,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:41.622Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--10093--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-b0d5c8656fd0d034a899",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "activity HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1648,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 49,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:42.499Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--3b469--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1649,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:43.374Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--3b469--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-cfd5e3da2402689b598c",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "production HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1650,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:44.325Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--d5363--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1651,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:45.186Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--d5363--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-fa8c596f2b087c2168ca",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "tour-planner HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1652,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:45.986Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--3aeb1--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1653,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 48,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:46.842Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--3aeb1--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-8847f9fba17633178ff2",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "tour-pg HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1654,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:47.719Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--7bdc5--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1655,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:48.646Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--7bdc5--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-b818497dccbcea86677b",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            },
            {
              "title": "hub HTML loads safe-bottom.css",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1656,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:49.528Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--5e9a6--HTML-loads-safe-bottom-css-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    },
                    {
                      "workerIndex": 1657,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26",
                        "location": {
                          "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                          "column": 26,
                          "line": 73
                        },
                        "snippet": "  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "column": 26,
                            "line": 73
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  71 |       const ctx = await request.newContext();\n  72 |       const r = await ctx.get(BASE + pwa.url);\n> 73 |       expect(r.status()).toBe(200);\n     |                          ^\n  74 |       const html = await r.text();\n  75 |       expect(html).toContain('/shared/safe-bottom.css');\n  76 |       await ctx.dispose();\n    at /var/www/360lm/tests/safe_bottom.spec.js:73:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:46:50.337Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/safe_bottom-Safe-bottom-—--5e9a6--HTML-loads-safe-bottom-css-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "column": 26,
                        "line": 73
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "4a86f770f33be3c594b9-a7f2902d59ded27b7994",
              "file": "safe_bottom.spec.js",
              "line": 70,
              "column": 5
            }
          ]
        },
        {
          "title": "Safe-bottom — computed padding-bottom ≥ 100px",
          "file": "safe_bottom.spec.js",
          "line": 84,
          "column": 6,
          "specs": [
            {
              "title": "expense main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for expense (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 766,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:50.033Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for expense (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-373853424f68bdea42c0",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "recce main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for recce (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 678,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:50.964Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for recce (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-509734cd0caa519f4c71",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "installation main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for installation (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 657,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:51.666Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for installation (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-0781851981e3dab9beb0",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "finance main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for finance (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 659,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:52.354Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for finance (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-de14babf1bf971a4bc2a",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "finance-upi main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for finance-upi (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 647,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:53.038Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for finance-upi (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-d4cd6f0f1b629d03fcf0",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "finance-credit-card main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for finance-credit-card (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 656,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:53.701Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for finance-credit-card (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-2c7faddb0ce7bf510739",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "finance-custodian main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for finance-custodian (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 651,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:54.374Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for finance-custodian (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-77a81ead3ce0c6212c14",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "finance-vendors main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for finance-vendors (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 672,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:55.046Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for finance-vendors (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-6ba898f9969e4501832e",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "sales main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for sales (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 644,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:55.736Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for sales (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-66c32c56af9101328fac",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "sales-customers main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for sales-customers (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 704,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:56.393Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for sales-customers (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-684cfe188304d039b74c",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "stores main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for stores (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 641,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:57.112Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for stores (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-7c6c274b70326b14d4a6",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "hr main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for hr (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 652,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:57.766Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for hr (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-283a68bfd5b999e50170",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "vehicle main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for vehicle (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 668,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:58.435Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for vehicle (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-7844b27769363cfef00d",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "admin main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for admin (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 664,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:59.119Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for admin (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-212d264fd5537b82c78b",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "activity main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for activity (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 681,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:46:59.802Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for activity (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-9d7aed3fce64679d73c0",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "production main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for production (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 757,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:00.502Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for production (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-72e90d087aa2016def7c",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "tour-planner main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for tour-planner (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 757,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:01.288Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for tour-planner (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-f9a27cc1adc5b7007a9a",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "tour-pg main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for tour-pg (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 807,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:02.070Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for tour-pg (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-f766c764a22f2a5c50f0",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "hub main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for hub (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 672,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:02.899Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for hub (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-de2ff7694556b616560d",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "expense main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for expense (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 754,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:51.262Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for expense (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-104d95f4eab88e365056",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "recce main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for recce (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 659,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:52.186Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for recce (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-b96d06826041934a620d",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "installation main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for installation (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 663,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:52.865Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for installation (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-2ea4be512dc72af6059a",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "finance main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for finance (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 653,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:53.549Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for finance (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-81d5c114d6ffc11defac",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "finance-upi main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for finance-upi (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 656,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:54.233Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for finance-upi (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-c1cf9061fde969d47a97",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "finance-credit-card main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for finance-credit-card (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 689,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:54.911Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for finance-credit-card (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-35b4bc066a46ee6df663",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "finance-custodian main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for finance-custodian (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 659,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:55.614Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for finance-custodian (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-d07cf26662caa44bf860",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "finance-vendors main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for finance-vendors (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 636,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:56.287Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for finance-vendors (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-1424dd6938b28b94dc81",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "sales main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for sales (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 659,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:56.936Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for sales (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-ec00cc771ad4018d7d92",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "sales-customers main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for sales-customers (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 667,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:57.607Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for sales-customers (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-87107eb8ffca31308e85",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "stores main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for stores (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 642,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:58.287Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for stores (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-9b86346bee6e11ed5420",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "hr main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for hr (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 655,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:58.945Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for hr (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-88bba891b947ee4812e8",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "vehicle main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for vehicle (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 661,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:46:59.617Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for vehicle (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-5468c2e5fb02db7d31c3",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "admin main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for admin (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 654,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:00.297Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for admin (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-47534c531bf8bba316b1",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "activity main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for activity (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 673,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:00.967Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for activity (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-c77860076f43609f27f5",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "production main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for production (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 853,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:01.658Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for production (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-0aff3c5504a0f7bc1eee",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "tour-planner main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for tour-planner (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 767,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:02.532Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for tour-planner (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-8260a09b209d3d80aa4b",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "tour-pg main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for tour-pg (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 708,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:03.330Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for tour-pg (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-bef6070062e93422ebd7",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            },
            {
              "title": "hub main container has ≥100px bottom padding",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "description": "No matching container for hub (probably login gate)",
                      "location": {
                        "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                        "line": 107,
                        "column": 14
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 658,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:04.053Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "description": "No matching container for hub (probably login gate)",
                          "location": {
                            "file": "/var/www/360lm/tests/safe_bottom.spec.js",
                            "line": 107,
                            "column": 14
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "4a86f770f33be3c594b9-d79489dbe77cbcfd146b",
              "file": "safe_bottom.spec.js",
              "line": 86,
              "column": 5
            }
          ]
        }
      ]
    },
    {
      "title": "sales_billing.spec.js",
      "file": "sales_billing.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Sales Billing — Smoke",
          "file": "sales_billing.spec.js",
          "line": 51,
          "column": 6,
          "specs": [
            {
              "title": "page returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 650,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 238,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:54:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 54
                        },
                        "snippet": "  52 |   test('page returns 200', async ({ page }) => {\n  53 |     const r = await page.goto(BASE_URL);\n> 54 |     expect(r?.status()).toBe(200);\n     |                         ^\n  55 |   });\n  56 |\n  57 |   test('SW version is current', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 54
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  52 |   test('page returns 200', async ({ page }) => {\n  53 |     const r = await page.goto(BASE_URL);\n> 54 |     expect(r?.status()).toBe(200);\n     |                         ^\n  55 |   });\n  56 |\n  57 |   test('SW version is current', async () => {\n    at /var/www/360lm/tests/sales_billing.spec.js:54:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:03.618Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-page-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-page-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 54
                      }
                    },
                    {
                      "workerIndex": 651,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 310,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:54:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 54
                        },
                        "snippet": "  52 |   test('page returns 200', async ({ page }) => {\n  53 |     const r = await page.goto(BASE_URL);\n> 54 |     expect(r?.status()).toBe(200);\n     |                         ^\n  55 |   });\n  56 |\n  57 |   test('SW version is current', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 54
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  52 |   test('page returns 200', async ({ page }) => {\n  53 |     const r = await page.goto(BASE_URL);\n> 54 |     expect(r?.status()).toBe(200);\n     |                         ^\n  55 |   });\n  56 |\n  57 |   test('SW version is current', async () => {\n    at /var/www/360lm/tests/sales_billing.spec.js:54:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:04.773Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-page-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-page-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 54
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-b2e1c3f63ccb67778f06",
              "file": "sales_billing.spec.js",
              "line": 52,
              "column": 3
            },
            {
              "title": "SW version is current",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 652,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 72,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:61:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 17,
                          "line": 61
                        },
                        "snippet": "  59 |     const r = await ctx.get(BASE_URL + 'sw.js');\n  60 |     const txt = await r.text();\n> 61 |     expect(txt).toMatch(/360sales-v\\d+/);\n     |                 ^\n  62 |     await ctx.dispose();\n  63 |   });\n  64 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 17,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  59 |     const r = await ctx.get(BASE_URL + 'sw.js');\n  60 |     const txt = await r.text();\n> 61 |     expect(txt).toMatch(/360sales-v\\d+/);\n     |                 ^\n  62 |     await ctx.dispose();\n  63 |   });\n  64 |\n    at /var/www/360lm/tests/sales_billing.spec.js:61:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:06.111Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-SW-version-is-current-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 17,
                        "line": 61
                      }
                    },
                    {
                      "workerIndex": 653,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 65,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:61:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 17,
                          "line": 61
                        },
                        "snippet": "  59 |     const r = await ctx.get(BASE_URL + 'sw.js');\n  60 |     const txt = await r.text();\n> 61 |     expect(txt).toMatch(/360sales-v\\d+/);\n     |                 ^\n  62 |     await ctx.dispose();\n  63 |   });\n  64 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 17,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  59 |     const r = await ctx.get(BASE_URL + 'sw.js');\n  60 |     const txt = await r.text();\n> 61 |     expect(txt).toMatch(/360sales-v\\d+/);\n     |                 ^\n  62 |     await ctx.dispose();\n  63 |   });\n  64 |\n    at /var/www/360lm/tests/sales_billing.spec.js:61:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:07.086Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-SW-version-is-current-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 17,
                        "line": 61
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-460cd72153ceac3f0c7f",
              "file": "sales_billing.spec.js",
              "line": 57,
              "column": 3
            },
            {
              "title": "HTML contains invoice + challan screens",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 654,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 71,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:69:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 69
                        },
                        "snippet": "  67 |     const r = await ctx.get(BASE_URL);\n  68 |     const html = await r.text();\n> 69 |     expect(html).toContain('screen-invoice-form');\n     |                  ^\n  70 |     expect(html).toContain('screen-challan-form');\n  71 |     expect(html).toContain('modal-payment');\n  72 |     expect(html).toContain('buildTaxInvoiceHtml');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 69
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  67 |     const r = await ctx.get(BASE_URL);\n  68 |     const html = await r.text();\n> 69 |     expect(html).toContain('screen-invoice-form');\n     |                  ^\n  70 |     expect(html).toContain('screen-challan-form');\n  71 |     expect(html).toContain('modal-payment');\n  72 |     expect(html).toContain('buildTaxInvoiceHtml');\n    at /var/www/360lm/tests/sales_billing.spec.js:69:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:08.007Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-3d778-ins-invoice-challan-screens-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 69
                      }
                    },
                    {
                      "workerIndex": 655,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:69:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 69
                        },
                        "snippet": "  67 |     const r = await ctx.get(BASE_URL);\n  68 |     const html = await r.text();\n> 69 |     expect(html).toContain('screen-invoice-form');\n     |                  ^\n  70 |     expect(html).toContain('screen-challan-form');\n  71 |     expect(html).toContain('modal-payment');\n  72 |     expect(html).toContain('buildTaxInvoiceHtml');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 69
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  67 |     const r = await ctx.get(BASE_URL);\n  68 |     const html = await r.text();\n> 69 |     expect(html).toContain('screen-invoice-form');\n     |                  ^\n  70 |     expect(html).toContain('screen-challan-form');\n  71 |     expect(html).toContain('modal-payment');\n  72 |     expect(html).toContain('buildTaxInvoiceHtml');\n    at /var/www/360lm/tests/sales_billing.spec.js:69:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:08.857Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-3d778-ins-invoice-challan-screens-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 69
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-f2cc72b9cc93f9eff3b5",
              "file": "sales_billing.spec.js",
              "line": 65,
              "column": 3
            },
            {
              "title": "page returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1658,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 217,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:54:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 54
                        },
                        "snippet": "  52 |   test('page returns 200', async ({ page }) => {\n  53 |     const r = await page.goto(BASE_URL);\n> 54 |     expect(r?.status()).toBe(200);\n     |                         ^\n  55 |   });\n  56 |\n  57 |   test('SW version is current', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 54
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  52 |   test('page returns 200', async ({ page }) => {\n  53 |     const r = await page.goto(BASE_URL);\n> 54 |     expect(r?.status()).toBe(200);\n     |                         ^\n  55 |   });\n  56 |\n  57 |   test('SW version is current', async () => {\n    at /var/www/360lm/tests/sales_billing.spec.js:54:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:04.744Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-page-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-page-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 54
                      }
                    },
                    {
                      "workerIndex": 1659,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 297,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:54:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 54
                        },
                        "snippet": "  52 |   test('page returns 200', async ({ page }) => {\n  53 |     const r = await page.goto(BASE_URL);\n> 54 |     expect(r?.status()).toBe(200);\n     |                         ^\n  55 |   });\n  56 |\n  57 |   test('SW version is current', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 54
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  52 |   test('page returns 200', async ({ page }) => {\n  53 |     const r = await page.goto(BASE_URL);\n> 54 |     expect(r?.status()).toBe(200);\n     |                         ^\n  55 |   });\n  56 |\n  57 |   test('SW version is current', async () => {\n    at /var/www/360lm/tests/sales_billing.spec.js:54:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:05.893Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-page-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-page-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 54
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-7bdf803ebcfe73b92882",
              "file": "sales_billing.spec.js",
              "line": 52,
              "column": 3
            },
            {
              "title": "SW version is current",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1660,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:61:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 17,
                          "line": 61
                        },
                        "snippet": "  59 |     const r = await ctx.get(BASE_URL + 'sw.js');\n  60 |     const txt = await r.text();\n> 61 |     expect(txt).toMatch(/360sales-v\\d+/);\n     |                 ^\n  62 |     await ctx.dispose();\n  63 |   });\n  64 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 17,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  59 |     const r = await ctx.get(BASE_URL + 'sw.js');\n  60 |     const txt = await r.text();\n> 61 |     expect(txt).toMatch(/360sales-v\\d+/);\n     |                 ^\n  62 |     await ctx.dispose();\n  63 |   });\n  64 |\n    at /var/www/360lm/tests/sales_billing.spec.js:61:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:07.208Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-SW-version-is-current-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 17,
                        "line": 61
                      }
                    },
                    {
                      "workerIndex": 1661,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:61:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 17,
                          "line": 61
                        },
                        "snippet": "  59 |     const r = await ctx.get(BASE_URL + 'sw.js');\n  60 |     const txt = await r.text();\n> 61 |     expect(txt).toMatch(/360sales-v\\d+/);\n     |                 ^\n  62 |     await ctx.dispose();\n  63 |   });\n  64 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 17,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  59 |     const r = await ctx.get(BASE_URL + 'sw.js');\n  60 |     const txt = await r.text();\n> 61 |     expect(txt).toMatch(/360sales-v\\d+/);\n     |                 ^\n  62 |     await ctx.dispose();\n  63 |   });\n  64 |\n    at /var/www/360lm/tests/sales_billing.spec.js:61:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:08.251Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billing-—-Smoke-SW-version-is-current-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 17,
                        "line": 61
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-487a12261d499eaa8af3",
              "file": "sales_billing.spec.js",
              "line": 57,
              "column": 3
            },
            {
              "title": "HTML contains invoice + challan screens",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1662,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:69:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 69
                        },
                        "snippet": "  67 |     const r = await ctx.get(BASE_URL);\n  68 |     const html = await r.text();\n> 69 |     expect(html).toContain('screen-invoice-form');\n     |                  ^\n  70 |     expect(html).toContain('screen-challan-form');\n  71 |     expect(html).toContain('modal-payment');\n  72 |     expect(html).toContain('buildTaxInvoiceHtml');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 69
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  67 |     const r = await ctx.get(BASE_URL);\n  68 |     const html = await r.text();\n> 69 |     expect(html).toContain('screen-invoice-form');\n     |                  ^\n  70 |     expect(html).toContain('screen-challan-form');\n  71 |     expect(html).toContain('modal-payment');\n  72 |     expect(html).toContain('buildTaxInvoiceHtml');\n    at /var/www/360lm/tests/sales_billing.spec.js:69:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:09.138Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-3d778-ins-invoice-challan-screens-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 69
                      }
                    },
                    {
                      "workerIndex": 1663,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:69:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 69
                        },
                        "snippet": "  67 |     const r = await ctx.get(BASE_URL);\n  68 |     const html = await r.text();\n> 69 |     expect(html).toContain('screen-invoice-form');\n     |                  ^\n  70 |     expect(html).toContain('screen-challan-form');\n  71 |     expect(html).toContain('modal-payment');\n  72 |     expect(html).toContain('buildTaxInvoiceHtml');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 69
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"screen-invoice-form\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  67 |     const r = await ctx.get(BASE_URL);\n  68 |     const html = await r.text();\n> 69 |     expect(html).toContain('screen-invoice-form');\n     |                  ^\n  70 |     expect(html).toContain('screen-challan-form');\n  71 |     expect(html).toContain('modal-payment');\n  72 |     expect(html).toContain('buildTaxInvoiceHtml');\n    at /var/www/360lm/tests/sales_billing.spec.js:69:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:10.152Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-3d778-ins-invoice-challan-screens-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 69
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-01a33095bab19da9e4b5",
              "file": "sales_billing.spec.js",
              "line": 65,
              "column": 3
            }
          ]
        },
        {
          "title": "Sales Billing — Stage A+B: Offer schema",
          "file": "sales_billing.spec.js",
          "line": 81,
          "column": 6,
          "specs": [
            {
              "title": "offer_lines has hsn_sac + line_type columns",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 656,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 94,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:84:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 84
                        },
                        "snippet": "  82 |   test('offer_lines has hsn_sac + line_type columns', async () => {\n  83 |     const rows = await dbGet('/offer_lines?offer_id=eq.bt-off-3&select=element,hsn_sac,line_type&order=sr_no');\n> 84 |     expect(rows.length).toBeGreaterThanOrEqual(4);\n     |                         ^\n  85 |     const svc = rows.find(r => r.line_type === 'service');\n  86 |     const mat = rows.find(r => r.line_type === 'material');\n  87 |     expect(svc).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 84
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  82 |   test('offer_lines has hsn_sac + line_type columns', async () => {\n  83 |     const rows = await dbGet('/offer_lines?offer_id=eq.bt-off-3&select=element,hsn_sac,line_type&order=sr_no');\n> 84 |     expect(rows.length).toBeGreaterThanOrEqual(4);\n     |                         ^\n  85 |     const svc = rows.find(r => r.line_type === 'service');\n  86 |     const mat = rows.find(r => r.line_type === 'material');\n  87 |     expect(svc).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:84:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:09.703Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-876eb-s-hsn-sac-line-type-columns-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 84
                      }
                    },
                    {
                      "workerIndex": 657,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:84:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 84
                        },
                        "snippet": "  82 |   test('offer_lines has hsn_sac + line_type columns', async () => {\n  83 |     const rows = await dbGet('/offer_lines?offer_id=eq.bt-off-3&select=element,hsn_sac,line_type&order=sr_no');\n> 84 |     expect(rows.length).toBeGreaterThanOrEqual(4);\n     |                         ^\n  85 |     const svc = rows.find(r => r.line_type === 'service');\n  86 |     const mat = rows.find(r => r.line_type === 'material');\n  87 |     expect(svc).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 84
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  82 |   test('offer_lines has hsn_sac + line_type columns', async () => {\n  83 |     const rows = await dbGet('/offer_lines?offer_id=eq.bt-off-3&select=element,hsn_sac,line_type&order=sr_no');\n> 84 |     expect(rows.length).toBeGreaterThanOrEqual(4);\n     |                         ^\n  85 |     const svc = rows.find(r => r.line_type === 'service');\n  86 |     const mat = rows.find(r => r.line_type === 'material');\n  87 |     expect(svc).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:84:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:10.659Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-876eb-s-hsn-sac-line-type-columns-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 84
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-c73a5c1c1d16a7cb20a3",
              "file": "sales_billing.spec.js",
              "line": 82,
              "column": 3
            },
            {
              "title": "offer_lines.amount is generated (rate*qty)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 658,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 62,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:11.557Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-48a092d4a1fd6b430390",
              "file": "sales_billing.spec.js",
              "line": 93,
              "column": 3
            },
            {
              "title": "accepted offer has po_number + po_date + po_amount",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 658,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 19,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:104:14",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 14,
                          "line": 104
                        },
                        "snippet": "  102 |     const rows = await dbGet('/offers?offer_id=eq.bt-off-3&select=status,po_number,po_date,po_amount,accepted_at');\n  103 |     const o = rows[0];\n> 104 |     expect(o.status).toBe('accepted');\n      |              ^\n  105 |     expect(o.po_number).toBe('PO/VERVE/2025-26/047');\n  106 |     expect(o.po_date).toBe('2026-06-01');\n  107 |     expect(Number(o.po_amount)).toBe(850000);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 14,
                            "line": 104
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  102 |     const rows = await dbGet('/offers?offer_id=eq.bt-off-3&select=status,po_number,po_date,po_amount,accepted_at');\n  103 |     const o = rows[0];\n> 104 |     expect(o.status).toBe('accepted');\n      |              ^\n  105 |     expect(o.po_number).toBe('PO/VERVE/2025-26/047');\n  106 |     expect(o.po_date).toBe('2026-06-01');\n  107 |     expect(Number(o.po_amount)).toBe(850000);\n    at /var/www/360lm/tests/sales_billing.spec.js:104:14"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:11.668Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-8e992-po-number-po-date-po-amount-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 14,
                        "line": 104
                      }
                    },
                    {
                      "workerIndex": 659,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:104:14",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 14,
                          "line": 104
                        },
                        "snippet": "  102 |     const rows = await dbGet('/offers?offer_id=eq.bt-off-3&select=status,po_number,po_date,po_amount,accepted_at');\n  103 |     const o = rows[0];\n> 104 |     expect(o.status).toBe('accepted');\n      |              ^\n  105 |     expect(o.po_number).toBe('PO/VERVE/2025-26/047');\n  106 |     expect(o.po_date).toBe('2026-06-01');\n  107 |     expect(Number(o.po_amount)).toBe(850000);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 14,
                            "line": 104
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  102 |     const rows = await dbGet('/offers?offer_id=eq.bt-off-3&select=status,po_number,po_date,po_amount,accepted_at');\n  103 |     const o = rows[0];\n> 104 |     expect(o.status).toBe('accepted');\n      |              ^\n  105 |     expect(o.po_number).toBe('PO/VERVE/2025-26/047');\n  106 |     expect(o.po_date).toBe('2026-06-01');\n  107 |     expect(Number(o.po_amount)).toBe(850000);\n    at /var/www/360lm/tests/sales_billing.spec.js:104:14"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:12.480Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-8e992-po-number-po-date-po-amount-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 14,
                        "line": 104
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-f3faf960e2a800ec43e0",
              "file": "sales_billing.spec.js",
              "line": 101,
              "column": 3
            },
            {
              "title": "HTML form has po_number + po_date + po_amount fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 660,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:114:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 114
                        },
                        "snippet": "  112 |     const ctx = await request.newContext();\n  113 |     const html = await (await ctx.get(BASE_URL)).text();\n> 114 |     expect(html).toContain('id=\"off-po-number\"');\n      |                  ^\n  115 |     expect(html).toContain('id=\"off-po-date\"');\n  116 |     expect(html).toContain('id=\"off-po-amount\"');\n  117 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 114
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  112 |     const ctx = await request.newContext();\n  113 |     const html = await (await ctx.get(BASE_URL)).text();\n> 114 |     expect(html).toContain('id=\"off-po-number\"');\n      |                  ^\n  115 |     expect(html).toContain('id=\"off-po-date\"');\n  116 |     expect(html).toContain('id=\"off-po-amount\"');\n  117 |     await ctx.dispose();\n    at /var/www/360lm/tests/sales_billing.spec.js:114:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:13.313Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-db99b-er-po-date-po-amount-fields-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 114
                      }
                    },
                    {
                      "workerIndex": 661,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:114:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 114
                        },
                        "snippet": "  112 |     const ctx = await request.newContext();\n  113 |     const html = await (await ctx.get(BASE_URL)).text();\n> 114 |     expect(html).toContain('id=\"off-po-number\"');\n      |                  ^\n  115 |     expect(html).toContain('id=\"off-po-date\"');\n  116 |     expect(html).toContain('id=\"off-po-amount\"');\n  117 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 114
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  112 |     const ctx = await request.newContext();\n  113 |     const html = await (await ctx.get(BASE_URL)).text();\n> 114 |     expect(html).toContain('id=\"off-po-number\"');\n      |                  ^\n  115 |     expect(html).toContain('id=\"off-po-date\"');\n  116 |     expect(html).toContain('id=\"off-po-amount\"');\n  117 |     await ctx.dispose();\n    at /var/www/360lm/tests/sales_billing.spec.js:114:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:14.152Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-db99b-er-po-date-po-amount-fields-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 114
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-219f3bfb2a6274531e36",
              "file": "sales_billing.spec.js",
              "line": 111,
              "column": 3
            },
            {
              "title": "company select visible on offer form (not just hidden input)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 662,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 48,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |     const ctx = await request.newContext();\n  122 |     const html = await (await ctx.get(BASE_URL)).text();\n> 123 |     expect(html).toContain('id=\"off-company-select\"');\n      |                  ^\n  124 |     await ctx.dispose();\n  125 |   });\n  126 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  121 |     const ctx = await request.newContext();\n  122 |     const html = await (await ctx.get(BASE_URL)).text();\n> 123 |     expect(html).toContain('id=\"off-company-select\"');\n      |                  ^\n  124 |     await ctx.dispose();\n  125 |   });\n  126 | });\n    at /var/www/360lm/tests/sales_billing.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:15.036Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-15035-form-not-just-hidden-input--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    },
                    {
                      "workerIndex": 663,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |     const ctx = await request.newContext();\n  122 |     const html = await (await ctx.get(BASE_URL)).text();\n> 123 |     expect(html).toContain('id=\"off-company-select\"');\n      |                  ^\n  124 |     await ctx.dispose();\n  125 |   });\n  126 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  121 |     const ctx = await request.newContext();\n  122 |     const html = await (await ctx.get(BASE_URL)).text();\n> 123 |     expect(html).toContain('id=\"off-company-select\"');\n      |                  ^\n  124 |     await ctx.dispose();\n  125 |   });\n  126 | });\n    at /var/www/360lm/tests/sales_billing.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:15.923Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-15035-form-not-just-hidden-input--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-8f7fe54045c9943e6182",
              "file": "sales_billing.spec.js",
              "line": 120,
              "column": 3
            },
            {
              "title": "offer_lines has hsn_sac + line_type columns",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1664,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 100,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:84:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 84
                        },
                        "snippet": "  82 |   test('offer_lines has hsn_sac + line_type columns', async () => {\n  83 |     const rows = await dbGet('/offer_lines?offer_id=eq.bt-off-3&select=element,hsn_sac,line_type&order=sr_no');\n> 84 |     expect(rows.length).toBeGreaterThanOrEqual(4);\n     |                         ^\n  85 |     const svc = rows.find(r => r.line_type === 'service');\n  86 |     const mat = rows.find(r => r.line_type === 'material');\n  87 |     expect(svc).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 84
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  82 |   test('offer_lines has hsn_sac + line_type columns', async () => {\n  83 |     const rows = await dbGet('/offer_lines?offer_id=eq.bt-off-3&select=element,hsn_sac,line_type&order=sr_no');\n> 84 |     expect(rows.length).toBeGreaterThanOrEqual(4);\n     |                         ^\n  85 |     const svc = rows.find(r => r.line_type === 'service');\n  86 |     const mat = rows.find(r => r.line_type === 'material');\n  87 |     expect(svc).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:84:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:11.070Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-876eb-s-hsn-sac-line-type-columns-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 84
                      }
                    },
                    {
                      "workerIndex": 1665,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 71,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:84:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 84
                        },
                        "snippet": "  82 |   test('offer_lines has hsn_sac + line_type columns', async () => {\n  83 |     const rows = await dbGet('/offer_lines?offer_id=eq.bt-off-3&select=element,hsn_sac,line_type&order=sr_no');\n> 84 |     expect(rows.length).toBeGreaterThanOrEqual(4);\n     |                         ^\n  85 |     const svc = rows.find(r => r.line_type === 'service');\n  86 |     const mat = rows.find(r => r.line_type === 'material');\n  87 |     expect(svc).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 84
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m4\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  82 |   test('offer_lines has hsn_sac + line_type columns', async () => {\n  83 |     const rows = await dbGet('/offer_lines?offer_id=eq.bt-off-3&select=element,hsn_sac,line_type&order=sr_no');\n> 84 |     expect(rows.length).toBeGreaterThanOrEqual(4);\n     |                         ^\n  85 |     const svc = rows.find(r => r.line_type === 'service');\n  86 |     const mat = rows.find(r => r.line_type === 'material');\n  87 |     expect(svc).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:84:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:12.102Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-876eb-s-hsn-sac-line-type-columns-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 84
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-89c28f86915253fc6540",
              "file": "sales_billing.spec.js",
              "line": 82,
              "column": 3
            },
            {
              "title": "offer_lines.amount is generated (rate*qty)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1666,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 55,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:13.071Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-ba02c85258c2ca998846",
              "file": "sales_billing.spec.js",
              "line": 93,
              "column": 3
            },
            {
              "title": "accepted offer has po_number + po_date + po_amount",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1666,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 27,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:104:14",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 14,
                          "line": 104
                        },
                        "snippet": "  102 |     const rows = await dbGet('/offers?offer_id=eq.bt-off-3&select=status,po_number,po_date,po_amount,accepted_at');\n  103 |     const o = rows[0];\n> 104 |     expect(o.status).toBe('accepted');\n      |              ^\n  105 |     expect(o.po_number).toBe('PO/VERVE/2025-26/047');\n  106 |     expect(o.po_date).toBe('2026-06-01');\n  107 |     expect(Number(o.po_amount)).toBe(850000);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 14,
                            "line": 104
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  102 |     const rows = await dbGet('/offers?offer_id=eq.bt-off-3&select=status,po_number,po_date,po_amount,accepted_at');\n  103 |     const o = rows[0];\n> 104 |     expect(o.status).toBe('accepted');\n      |              ^\n  105 |     expect(o.po_number).toBe('PO/VERVE/2025-26/047');\n  106 |     expect(o.po_date).toBe('2026-06-01');\n  107 |     expect(Number(o.po_amount)).toBe(850000);\n    at /var/www/360lm/tests/sales_billing.spec.js:104:14"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:13.170Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-8e992-po-number-po-date-po-amount-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 14,
                        "line": 104
                      }
                    },
                    {
                      "workerIndex": 1667,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:104:14",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 14,
                          "line": 104
                        },
                        "snippet": "  102 |     const rows = await dbGet('/offers?offer_id=eq.bt-off-3&select=status,po_number,po_date,po_amount,accepted_at');\n  103 |     const o = rows[0];\n> 104 |     expect(o.status).toBe('accepted');\n      |              ^\n  105 |     expect(o.po_number).toBe('PO/VERVE/2025-26/047');\n  106 |     expect(o.po_date).toBe('2026-06-01');\n  107 |     expect(Number(o.po_amount)).toBe(850000);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 14,
                            "line": 104
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  102 |     const rows = await dbGet('/offers?offer_id=eq.bt-off-3&select=status,po_number,po_date,po_amount,accepted_at');\n  103 |     const o = rows[0];\n> 104 |     expect(o.status).toBe('accepted');\n      |              ^\n  105 |     expect(o.po_number).toBe('PO/VERVE/2025-26/047');\n  106 |     expect(o.po_date).toBe('2026-06-01');\n  107 |     expect(Number(o.po_amount)).toBe(850000);\n    at /var/www/360lm/tests/sales_billing.spec.js:104:14"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:13.982Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-8e992-po-number-po-date-po-amount-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 14,
                        "line": 104
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-0e753758df5f00116801",
              "file": "sales_billing.spec.js",
              "line": 101,
              "column": 3
            },
            {
              "title": "HTML form has po_number + po_date + po_amount fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1668,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:114:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 114
                        },
                        "snippet": "  112 |     const ctx = await request.newContext();\n  113 |     const html = await (await ctx.get(BASE_URL)).text();\n> 114 |     expect(html).toContain('id=\"off-po-number\"');\n      |                  ^\n  115 |     expect(html).toContain('id=\"off-po-date\"');\n  116 |     expect(html).toContain('id=\"off-po-amount\"');\n  117 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 114
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  112 |     const ctx = await request.newContext();\n  113 |     const html = await (await ctx.get(BASE_URL)).text();\n> 114 |     expect(html).toContain('id=\"off-po-number\"');\n      |                  ^\n  115 |     expect(html).toContain('id=\"off-po-date\"');\n  116 |     expect(html).toContain('id=\"off-po-amount\"');\n  117 |     await ctx.dispose();\n    at /var/www/360lm/tests/sales_billing.spec.js:114:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:14.895Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-db99b-er-po-date-po-amount-fields-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 114
                      }
                    },
                    {
                      "workerIndex": 1669,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:114:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 114
                        },
                        "snippet": "  112 |     const ctx = await request.newContext();\n  113 |     const html = await (await ctx.get(BASE_URL)).text();\n> 114 |     expect(html).toContain('id=\"off-po-number\"');\n      |                  ^\n  115 |     expect(html).toContain('id=\"off-po-date\"');\n  116 |     expect(html).toContain('id=\"off-po-amount\"');\n  117 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 114
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-po-number\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  112 |     const ctx = await request.newContext();\n  113 |     const html = await (await ctx.get(BASE_URL)).text();\n> 114 |     expect(html).toContain('id=\"off-po-number\"');\n      |                  ^\n  115 |     expect(html).toContain('id=\"off-po-date\"');\n  116 |     expect(html).toContain('id=\"off-po-amount\"');\n  117 |     await ctx.dispose();\n    at /var/www/360lm/tests/sales_billing.spec.js:114:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:15.823Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-db99b-er-po-date-po-amount-fields-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 114
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-3862404d1881dd50a29c",
              "file": "sales_billing.spec.js",
              "line": 111,
              "column": 3
            },
            {
              "title": "company select visible on offer form (not just hidden input)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1670,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |     const ctx = await request.newContext();\n  122 |     const html = await (await ctx.get(BASE_URL)).text();\n> 123 |     expect(html).toContain('id=\"off-company-select\"');\n      |                  ^\n  124 |     await ctx.dispose();\n  125 |   });\n  126 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  121 |     const ctx = await request.newContext();\n  122 |     const html = await (await ctx.get(BASE_URL)).text();\n> 123 |     expect(html).toContain('id=\"off-company-select\"');\n      |                  ^\n  124 |     await ctx.dispose();\n  125 |   });\n  126 | });\n    at /var/www/360lm/tests/sales_billing.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:16.722Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-15035-form-not-just-hidden-input--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    },
                    {
                      "workerIndex": 1671,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:123:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 123
                        },
                        "snippet": "  121 |     const ctx = await request.newContext();\n  122 |     const html = await (await ctx.get(BASE_URL)).text();\n> 123 |     expect(html).toContain('id=\"off-company-select\"');\n      |                  ^\n  124 |     await ctx.dispose();\n  125 |   });\n  126 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 123
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"off-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  121 |     const ctx = await request.newContext();\n  122 |     const html = await (await ctx.get(BASE_URL)).text();\n> 123 |     expect(html).toContain('id=\"off-company-select\"');\n      |                  ^\n  124 |     await ctx.dispose();\n  125 |   });\n  126 | });\n    at /var/www/360lm/tests/sales_billing.spec.js:123:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:17.721Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-15035-form-not-just-hidden-input--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 123
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-a607723350d4f6e376e0",
              "file": "sales_billing.spec.js",
              "line": 120,
              "column": 3
            }
          ]
        },
        {
          "title": "Sales Billing — Stage E: Invoices",
          "file": "sales_billing.spec.js",
          "line": 131,
          "column": 6,
          "specs": [
            {
              "title": "three test invoices exist with correct statuses",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 664,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:134:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 134
                        },
                        "snippet": "  132 |   test('three test invoices exist with correct statuses', async () => {\n  133 |     const rows = await dbGet('/invoices?invoice_id=in.(bt-inv-1,bt-inv-2,bt-inv-3)&select=invoice_id,invoice_no,status,company,gst_type&order=invoice_id');\n> 134 |     expect(rows.length).toBe(3);\n      |                         ^\n  135 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  136 |     expect(byId['bt-inv-1'].status).toBe('draft');\n  137 |     expect(byId['bt-inv-2'].status).toBe('issued');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 134
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  132 |   test('three test invoices exist with correct statuses', async () => {\n  133 |     const rows = await dbGet('/invoices?invoice_id=in.(bt-inv-1,bt-inv-2,bt-inv-3)&select=invoice_id,invoice_no,status,company,gst_type&order=invoice_id');\n> 134 |     expect(rows.length).toBe(3);\n      |                         ^\n  135 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  136 |     expect(byId['bt-inv-1'].status).toBe('draft');\n  137 |     expect(byId['bt-inv-2'].status).toBe('issued');\n    at /var/www/360lm/tests/sales_billing.spec.js:134:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:16.773Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-d46dc-exist-with-correct-statuses-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 134
                      }
                    },
                    {
                      "workerIndex": 665,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:134:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 134
                        },
                        "snippet": "  132 |   test('three test invoices exist with correct statuses', async () => {\n  133 |     const rows = await dbGet('/invoices?invoice_id=in.(bt-inv-1,bt-inv-2,bt-inv-3)&select=invoice_id,invoice_no,status,company,gst_type&order=invoice_id');\n> 134 |     expect(rows.length).toBe(3);\n      |                         ^\n  135 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  136 |     expect(byId['bt-inv-1'].status).toBe('draft');\n  137 |     expect(byId['bt-inv-2'].status).toBe('issued');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 134
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  132 |   test('three test invoices exist with correct statuses', async () => {\n  133 |     const rows = await dbGet('/invoices?invoice_id=in.(bt-inv-1,bt-inv-2,bt-inv-3)&select=invoice_id,invoice_no,status,company,gst_type&order=invoice_id');\n> 134 |     expect(rows.length).toBe(3);\n      |                         ^\n  135 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  136 |     expect(byId['bt-inv-1'].status).toBe('draft');\n  137 |     expect(byId['bt-inv-2'].status).toBe('issued');\n    at /var/www/360lm/tests/sales_billing.spec.js:134:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:17.685Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-d46dc-exist-with-correct-statuses-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 134
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-5e01ca4365b0dc64c89f",
              "file": "sales_billing.spec.js",
              "line": 132,
              "column": 3
            },
            {
              "title": "invoice_lines have hsn_sac + amount GENERATED correctly",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 666,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:145:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 145
                        },
                        "snippet": "  143 |   test('invoice_lines have hsn_sac + amount GENERATED correctly', async () => {\n  144 |     const rows = await dbGet('/invoice_lines?invoice_id=eq.bt-inv-2&select=element,rate,qty,amount,hsn_sac,line_type&order=sr_no');\n> 145 |     expect(rows.length).toBe(2);\n      |                         ^\n  146 |     rows.forEach(r => {\n  147 |       expect(Number(r.amount)).toBeCloseTo(Number(r.rate) * Number(r.qty), 1);\n  148 |       expect(r.hsn_sac).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 145
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  143 |   test('invoice_lines have hsn_sac + amount GENERATED correctly', async () => {\n  144 |     const rows = await dbGet('/invoice_lines?invoice_id=eq.bt-inv-2&select=element,rate,qty,amount,hsn_sac,line_type&order=sr_no');\n> 145 |     expect(rows.length).toBe(2);\n      |                         ^\n  146 |     rows.forEach(r => {\n  147 |       expect(Number(r.amount)).toBeCloseTo(Number(r.rate) * Number(r.qty), 1);\n  148 |       expect(r.hsn_sac).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:145:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:18.586Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-63eb2--amount-GENERATED-correctly-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 145
                      }
                    },
                    {
                      "workerIndex": 667,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 67,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:145:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 145
                        },
                        "snippet": "  143 |   test('invoice_lines have hsn_sac + amount GENERATED correctly', async () => {\n  144 |     const rows = await dbGet('/invoice_lines?invoice_id=eq.bt-inv-2&select=element,rate,qty,amount,hsn_sac,line_type&order=sr_no');\n> 145 |     expect(rows.length).toBe(2);\n      |                         ^\n  146 |     rows.forEach(r => {\n  147 |       expect(Number(r.amount)).toBeCloseTo(Number(r.rate) * Number(r.qty), 1);\n  148 |       expect(r.hsn_sac).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 145
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  143 |   test('invoice_lines have hsn_sac + amount GENERATED correctly', async () => {\n  144 |     const rows = await dbGet('/invoice_lines?invoice_id=eq.bt-inv-2&select=element,rate,qty,amount,hsn_sac,line_type&order=sr_no');\n> 145 |     expect(rows.length).toBe(2);\n      |                         ^\n  146 |     rows.forEach(r => {\n  147 |       expect(Number(r.amount)).toBeCloseTo(Number(r.rate) * Number(r.qty), 1);\n  148 |       expect(r.hsn_sac).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:145:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:19.510Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-63eb2--amount-GENERATED-correctly-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 145
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-492f2479ee1ea452ea4c",
              "file": "sales_billing.spec.js",
              "line": 143,
              "column": 3
            },
            {
              "title": "invoice_no is FY-aware per company",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 668,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 65,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:158:29",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 29,
                          "line": 158
                        },
                        "snippet": "  156 |     const pms = rows.find(r => r.company === 'pms');\n  157 |     const dlm = rows.find(r => r.company === '360dlm');\n> 158 |     expect(pms?.invoice_no).toContain('PMS');\n      |                             ^\n  159 |     expect(dlm?.invoice_no).toContain('360DLM');\n  160 |     // Both contain a FY-style string YY-YY\n  161 |     [pms, dlm].forEach(r => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 29,
                            "line": 158
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m\n\n  156 |     const pms = rows.find(r => r.company === 'pms');\n  157 |     const dlm = rows.find(r => r.company === '360dlm');\n> 158 |     expect(pms?.invoice_no).toContain('PMS');\n      |                             ^\n  159 |     expect(dlm?.invoice_no).toContain('360DLM');\n  160 |     // Both contain a FY-style string YY-YY\n  161 |     [pms, dlm].forEach(r => {\n    at /var/www/360lm/tests/sales_billing.spec.js:158:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:20.463Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-80868--no-is-FY-aware-per-company-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 29,
                        "line": 158
                      }
                    },
                    {
                      "workerIndex": 669,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 72,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:158:29",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 29,
                          "line": 158
                        },
                        "snippet": "  156 |     const pms = rows.find(r => r.company === 'pms');\n  157 |     const dlm = rows.find(r => r.company === '360dlm');\n> 158 |     expect(pms?.invoice_no).toContain('PMS');\n      |                             ^\n  159 |     expect(dlm?.invoice_no).toContain('360DLM');\n  160 |     // Both contain a FY-style string YY-YY\n  161 |     [pms, dlm].forEach(r => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 29,
                            "line": 158
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m\n\n  156 |     const pms = rows.find(r => r.company === 'pms');\n  157 |     const dlm = rows.find(r => r.company === '360dlm');\n> 158 |     expect(pms?.invoice_no).toContain('PMS');\n      |                             ^\n  159 |     expect(dlm?.invoice_no).toContain('360DLM');\n  160 |     // Both contain a FY-style string YY-YY\n  161 |     [pms, dlm].forEach(r => {\n    at /var/www/360lm/tests/sales_billing.spec.js:158:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:21.403Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-80868--no-is-FY-aware-per-company-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 29,
                        "line": 158
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-cc786299a0f23e219f93",
              "file": "sales_billing.spec.js",
              "line": 154,
              "column": 3
            },
            {
              "title": "issued invoice has issued_at stamped",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 670,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:169:16",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 16,
                          "line": 169
                        },
                        "snippet": "  167 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,issued_at,paid_at');\n  168 |     const inv = rows[0];\n> 169 |     expect(inv.status).toBe('issued');\n      |                ^\n  170 |     expect(inv.issued_at).toBeTruthy();\n  171 |     expect(inv.paid_at).toBeNull();\n  172 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 169
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  167 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,issued_at,paid_at');\n  168 |     const inv = rows[0];\n> 169 |     expect(inv.status).toBe('issued');\n      |                ^\n  170 |     expect(inv.issued_at).toBeTruthy();\n  171 |     expect(inv.paid_at).toBeNull();\n  172 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:169:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:22.322Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e2c-voice-has-issued-at-stamped-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 16,
                        "line": 169
                      }
                    },
                    {
                      "workerIndex": 671,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 68,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:169:16",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 16,
                          "line": 169
                        },
                        "snippet": "  167 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,issued_at,paid_at');\n  168 |     const inv = rows[0];\n> 169 |     expect(inv.status).toBe('issued');\n      |                ^\n  170 |     expect(inv.issued_at).toBeTruthy();\n  171 |     expect(inv.paid_at).toBeNull();\n  172 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 169
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  167 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,issued_at,paid_at');\n  168 |     const inv = rows[0];\n> 169 |     expect(inv.status).toBe('issued');\n      |                ^\n  170 |     expect(inv.issued_at).toBeTruthy();\n  171 |     expect(inv.paid_at).toBeNull();\n  172 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:169:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:23.279Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e2c-voice-has-issued-at-stamped-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 16,
                        "line": 169
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-d8d8e5509c582d41250c",
              "file": "sales_billing.spec.js",
              "line": 166,
              "column": 3
            },
            {
              "title": "paid invoice has both issued_at and paid_at",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 672,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:177:16",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 16,
                          "line": 177
                        },
                        "snippet": "  175 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-3&select=status,issued_at,paid_at');\n  176 |     const inv = rows[0];\n> 177 |     expect(inv.status).toBe('paid');\n      |                ^\n  178 |     expect(inv.issued_at).toBeTruthy();\n  179 |     expect(inv.paid_at).toBeTruthy();\n  180 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 177
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  175 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-3&select=status,issued_at,paid_at');\n  176 |     const inv = rows[0];\n> 177 |     expect(inv.status).toBe('paid');\n      |                ^\n  178 |     expect(inv.issued_at).toBeTruthy();\n  179 |     expect(inv.paid_at).toBeTruthy();\n  180 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:177:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:24.148Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-aa91b--both-issued-at-and-paid-at-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 16,
                        "line": 177
                      }
                    },
                    {
                      "workerIndex": 673,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:177:16",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 16,
                          "line": 177
                        },
                        "snippet": "  175 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-3&select=status,issued_at,paid_at');\n  176 |     const inv = rows[0];\n> 177 |     expect(inv.status).toBe('paid');\n      |                ^\n  178 |     expect(inv.issued_at).toBeTruthy();\n  179 |     expect(inv.paid_at).toBeTruthy();\n  180 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 177
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  175 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-3&select=status,issued_at,paid_at');\n  176 |     const inv = rows[0];\n> 177 |     expect(inv.status).toBe('paid');\n      |                ^\n  178 |     expect(inv.issued_at).toBeTruthy();\n  179 |     expect(inv.paid_at).toBeTruthy();\n  180 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:177:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:25.035Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-aa91b--both-issued-at-and-paid-at-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 16,
                        "line": 177
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-9bfd631fbbe61c678055",
              "file": "sales_billing.spec.js",
              "line": 174,
              "column": 3
            },
            {
              "title": "invoice KPI reflects billing test data",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 674,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 68,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:185:41",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 41,
                          "line": 185
                        },
                        "snippet": "  183 |     const rows = await dbGet('/invoice_kpi');\n  184 |     const kpi = rows[0];\n> 185 |     expect(Number(kpi.invoices_issued)).toBeGreaterThanOrEqual(2); // bt-inv-2 + bt-inv-3\n      |                                         ^\n  186 |     expect(Number(kpi.billed_total)).toBeGreaterThan(0);\n  187 |     expect(Number(kpi.collected_total)).toBeGreaterThan(0);\n  188 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 41,
                            "line": 185
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  183 |     const rows = await dbGet('/invoice_kpi');\n  184 |     const kpi = rows[0];\n> 185 |     expect(Number(kpi.invoices_issued)).toBeGreaterThanOrEqual(2); // bt-inv-2 + bt-inv-3\n      |                                         ^\n  186 |     expect(Number(kpi.billed_total)).toBeGreaterThan(0);\n  187 |     expect(Number(kpi.collected_total)).toBeGreaterThan(0);\n  188 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:185:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:25.944Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f95b5--reflects-billing-test-data-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 41,
                        "line": 185
                      }
                    },
                    {
                      "workerIndex": 675,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:185:41",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 41,
                          "line": 185
                        },
                        "snippet": "  183 |     const rows = await dbGet('/invoice_kpi');\n  184 |     const kpi = rows[0];\n> 185 |     expect(Number(kpi.invoices_issued)).toBeGreaterThanOrEqual(2); // bt-inv-2 + bt-inv-3\n      |                                         ^\n  186 |     expect(Number(kpi.billed_total)).toBeGreaterThan(0);\n  187 |     expect(Number(kpi.collected_total)).toBeGreaterThan(0);\n  188 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 41,
                            "line": 185
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  183 |     const rows = await dbGet('/invoice_kpi');\n  184 |     const kpi = rows[0];\n> 185 |     expect(Number(kpi.invoices_issued)).toBeGreaterThanOrEqual(2); // bt-inv-2 + bt-inv-3\n      |                                         ^\n  186 |     expect(Number(kpi.billed_total)).toBeGreaterThan(0);\n  187 |     expect(Number(kpi.collected_total)).toBeGreaterThan(0);\n  188 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:185:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:26.841Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f95b5--reflects-billing-test-data-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 41,
                        "line": 185
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-ffe2cfe8c955a0605499",
              "file": "sales_billing.spec.js",
              "line": 182,
              "column": 3
            },
            {
              "title": "Invoices tab button present in Job Detail HTML",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 676,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:193:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 193
                        },
                        "snippet": "  191 |     const ctx = await request.newContext();\n  192 |     const html = await (await ctx.get(BASE_URL)).text();\n> 193 |     expect(html).toContain('renderInvoicesTab()');\n      |                  ^\n  194 |     expect(html).toContain('id=\"tab-invoices\"');\n  195 |     expect(html).toContain('id=\"inv-form-title\"');\n  196 |     expect(html).toContain('id=\"inv-lines-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 193
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  191 |     const ctx = await request.newContext();\n  192 |     const html = await (await ctx.get(BASE_URL)).text();\n> 193 |     expect(html).toContain('renderInvoicesTab()');\n      |                  ^\n  194 |     expect(html).toContain('id=\"tab-invoices\"');\n  195 |     expect(html).toContain('id=\"inv-form-title\"');\n  196 |     expect(html).toContain('id=\"inv-lines-container\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:193:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:27.659Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-432ce--present-in-Job-Detail-HTML-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 193
                      }
                    },
                    {
                      "workerIndex": 677,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:193:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 193
                        },
                        "snippet": "  191 |     const ctx = await request.newContext();\n  192 |     const html = await (await ctx.get(BASE_URL)).text();\n> 193 |     expect(html).toContain('renderInvoicesTab()');\n      |                  ^\n  194 |     expect(html).toContain('id=\"tab-invoices\"');\n  195 |     expect(html).toContain('id=\"inv-form-title\"');\n  196 |     expect(html).toContain('id=\"inv-lines-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 193
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  191 |     const ctx = await request.newContext();\n  192 |     const html = await (await ctx.get(BASE_URL)).text();\n> 193 |     expect(html).toContain('renderInvoicesTab()');\n      |                  ^\n  194 |     expect(html).toContain('id=\"tab-invoices\"');\n  195 |     expect(html).toContain('id=\"inv-form-title\"');\n  196 |     expect(html).toContain('id=\"inv-lines-container\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:193:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:28.532Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-432ce--present-in-Job-Detail-HTML-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 193
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-77f9a702eebab64128aa",
              "file": "sales_billing.spec.js",
              "line": 190,
              "column": 3
            },
            {
              "title": "buildTaxInvoiceHtml exists in Sales JS",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 678,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:203:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 203
                        },
                        "snippet": "  201 |     const ctx = await request.newContext();\n  202 |     const html = await (await ctx.get(BASE_URL)).text();\n> 203 |     expect(html).toContain('function buildTaxInvoiceHtml');\n      |                  ^\n  204 |     expect(html).toContain('ORIGINAL FOR RECIPIENT');\n  205 |     expect(html).toContain('amountInWords');\n  206 |     expect(html).toContain('GST Rule 55'); // in challan builder nearby"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 203
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  201 |     const ctx = await request.newContext();\n  202 |     const html = await (await ctx.get(BASE_URL)).text();\n> 203 |     expect(html).toContain('function buildTaxInvoiceHtml');\n      |                  ^\n  204 |     expect(html).toContain('ORIGINAL FOR RECIPIENT');\n  205 |     expect(html).toContain('amountInWords');\n  206 |     expect(html).toContain('GST Rule 55'); // in challan builder nearby\n    at /var/www/360lm/tests/sales_billing.spec.js:203:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:29.469Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-1d72e-oiceHtml-exists-in-Sales-JS-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 203
                      }
                    },
                    {
                      "workerIndex": 679,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:203:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 203
                        },
                        "snippet": "  201 |     const ctx = await request.newContext();\n  202 |     const html = await (await ctx.get(BASE_URL)).text();\n> 203 |     expect(html).toContain('function buildTaxInvoiceHtml');\n      |                  ^\n  204 |     expect(html).toContain('ORIGINAL FOR RECIPIENT');\n  205 |     expect(html).toContain('amountInWords');\n  206 |     expect(html).toContain('GST Rule 55'); // in challan builder nearby"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 203
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  201 |     const ctx = await request.newContext();\n  202 |     const html = await (await ctx.get(BASE_URL)).text();\n> 203 |     expect(html).toContain('function buildTaxInvoiceHtml');\n      |                  ^\n  204 |     expect(html).toContain('ORIGINAL FOR RECIPIENT');\n  205 |     expect(html).toContain('amountInWords');\n  206 |     expect(html).toContain('GST Rule 55'); // in challan builder nearby\n    at /var/www/360lm/tests/sales_billing.spec.js:203:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:30.327Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-1d72e-oiceHtml-exists-in-Sales-JS-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 203
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-2a66d65a5db623d5cfd3",
              "file": "sales_billing.spec.js",
              "line": 200,
              "column": 3
            },
            {
              "title": "invoice tab visible in Job Detail screen (rendered UI)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 214,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 680,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:31.182Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 214,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-c6f67048ea174871ca18",
              "file": "sales_billing.spec.js",
              "line": 214,
              "column": 8
            },
            {
              "title": "three test invoices exist with correct statuses",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1672,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:134:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 134
                        },
                        "snippet": "  132 |   test('three test invoices exist with correct statuses', async () => {\n  133 |     const rows = await dbGet('/invoices?invoice_id=in.(bt-inv-1,bt-inv-2,bt-inv-3)&select=invoice_id,invoice_no,status,company,gst_type&order=invoice_id');\n> 134 |     expect(rows.length).toBe(3);\n      |                         ^\n  135 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  136 |     expect(byId['bt-inv-1'].status).toBe('draft');\n  137 |     expect(byId['bt-inv-2'].status).toBe('issued');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 134
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  132 |   test('three test invoices exist with correct statuses', async () => {\n  133 |     const rows = await dbGet('/invoices?invoice_id=in.(bt-inv-1,bt-inv-2,bt-inv-3)&select=invoice_id,invoice_no,status,company,gst_type&order=invoice_id');\n> 134 |     expect(rows.length).toBe(3);\n      |                         ^\n  135 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  136 |     expect(byId['bt-inv-1'].status).toBe('draft');\n  137 |     expect(byId['bt-inv-2'].status).toBe('issued');\n    at /var/www/360lm/tests/sales_billing.spec.js:134:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:18.592Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-d46dc-exist-with-correct-statuses-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 134
                      }
                    },
                    {
                      "workerIndex": 1673,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:134:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 134
                        },
                        "snippet": "  132 |   test('three test invoices exist with correct statuses', async () => {\n  133 |     const rows = await dbGet('/invoices?invoice_id=in.(bt-inv-1,bt-inv-2,bt-inv-3)&select=invoice_id,invoice_no,status,company,gst_type&order=invoice_id');\n> 134 |     expect(rows.length).toBe(3);\n      |                         ^\n  135 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  136 |     expect(byId['bt-inv-1'].status).toBe('draft');\n  137 |     expect(byId['bt-inv-2'].status).toBe('issued');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 134
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  132 |   test('three test invoices exist with correct statuses', async () => {\n  133 |     const rows = await dbGet('/invoices?invoice_id=in.(bt-inv-1,bt-inv-2,bt-inv-3)&select=invoice_id,invoice_no,status,company,gst_type&order=invoice_id');\n> 134 |     expect(rows.length).toBe(3);\n      |                         ^\n  135 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  136 |     expect(byId['bt-inv-1'].status).toBe('draft');\n  137 |     expect(byId['bt-inv-2'].status).toBe('issued');\n    at /var/www/360lm/tests/sales_billing.spec.js:134:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:19.466Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-d46dc-exist-with-correct-statuses-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 134
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-0a5c67733c7a3b009770",
              "file": "sales_billing.spec.js",
              "line": 132,
              "column": 3
            },
            {
              "title": "invoice_lines have hsn_sac + amount GENERATED correctly",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1674,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 72,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:145:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 145
                        },
                        "snippet": "  143 |   test('invoice_lines have hsn_sac + amount GENERATED correctly', async () => {\n  144 |     const rows = await dbGet('/invoice_lines?invoice_id=eq.bt-inv-2&select=element,rate,qty,amount,hsn_sac,line_type&order=sr_no');\n> 145 |     expect(rows.length).toBe(2);\n      |                         ^\n  146 |     rows.forEach(r => {\n  147 |       expect(Number(r.amount)).toBeCloseTo(Number(r.rate) * Number(r.qty), 1);\n  148 |       expect(r.hsn_sac).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 145
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  143 |   test('invoice_lines have hsn_sac + amount GENERATED correctly', async () => {\n  144 |     const rows = await dbGet('/invoice_lines?invoice_id=eq.bt-inv-2&select=element,rate,qty,amount,hsn_sac,line_type&order=sr_no');\n> 145 |     expect(rows.length).toBe(2);\n      |                         ^\n  146 |     rows.forEach(r => {\n  147 |       expect(Number(r.amount)).toBeCloseTo(Number(r.rate) * Number(r.qty), 1);\n  148 |       expect(r.hsn_sac).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:145:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:20.389Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-63eb2--amount-GENERATED-correctly-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 145
                      }
                    },
                    {
                      "workerIndex": 1675,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:145:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 145
                        },
                        "snippet": "  143 |   test('invoice_lines have hsn_sac + amount GENERATED correctly', async () => {\n  144 |     const rows = await dbGet('/invoice_lines?invoice_id=eq.bt-inv-2&select=element,rate,qty,amount,hsn_sac,line_type&order=sr_no');\n> 145 |     expect(rows.length).toBe(2);\n      |                         ^\n  146 |     rows.forEach(r => {\n  147 |       expect(Number(r.amount)).toBeCloseTo(Number(r.rate) * Number(r.qty), 1);\n  148 |       expect(r.hsn_sac).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 145
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m2\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  143 |   test('invoice_lines have hsn_sac + amount GENERATED correctly', async () => {\n  144 |     const rows = await dbGet('/invoice_lines?invoice_id=eq.bt-inv-2&select=element,rate,qty,amount,hsn_sac,line_type&order=sr_no');\n> 145 |     expect(rows.length).toBe(2);\n      |                         ^\n  146 |     rows.forEach(r => {\n  147 |       expect(Number(r.amount)).toBeCloseTo(Number(r.rate) * Number(r.qty), 1);\n  148 |       expect(r.hsn_sac).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:145:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:21.338Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-63eb2--amount-GENERATED-correctly-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 145
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-1a5f8be11db218732130",
              "file": "sales_billing.spec.js",
              "line": 143,
              "column": 3
            },
            {
              "title": "invoice_no is FY-aware per company",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1676,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 76,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:158:29",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 29,
                          "line": 158
                        },
                        "snippet": "  156 |     const pms = rows.find(r => r.company === 'pms');\n  157 |     const dlm = rows.find(r => r.company === '360dlm');\n> 158 |     expect(pms?.invoice_no).toContain('PMS');\n      |                             ^\n  159 |     expect(dlm?.invoice_no).toContain('360DLM');\n  160 |     // Both contain a FY-style string YY-YY\n  161 |     [pms, dlm].forEach(r => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 29,
                            "line": 158
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m\n\n  156 |     const pms = rows.find(r => r.company === 'pms');\n  157 |     const dlm = rows.find(r => r.company === '360dlm');\n> 158 |     expect(pms?.invoice_no).toContain('PMS');\n      |                             ^\n  159 |     expect(dlm?.invoice_no).toContain('360DLM');\n  160 |     // Both contain a FY-style string YY-YY\n  161 |     [pms, dlm].forEach(r => {\n    at /var/www/360lm/tests/sales_billing.spec.js:158:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:22.269Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-80868--no-is-FY-aware-per-company-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 29,
                        "line": 158
                      }
                    },
                    {
                      "workerIndex": 1677,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:158:29",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 29,
                          "line": 158
                        },
                        "snippet": "  156 |     const pms = rows.find(r => r.company === 'pms');\n  157 |     const dlm = rows.find(r => r.company === '360dlm');\n> 158 |     expect(pms?.invoice_no).toContain('PMS');\n      |                             ^\n  159 |     expect(dlm?.invoice_no).toContain('360DLM');\n  160 |     // Both contain a FY-style string YY-YY\n  161 |     [pms, dlm].forEach(r => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 29,
                            "line": 158
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\n\u001b[1mMatcher error\u001b[22m: \u001b[31mreceived\u001b[39m value must not be null nor undefined\n\nReceived has value: \u001b[31mundefined\u001b[39m\n\n  156 |     const pms = rows.find(r => r.company === 'pms');\n  157 |     const dlm = rows.find(r => r.company === '360dlm');\n> 158 |     expect(pms?.invoice_no).toContain('PMS');\n      |                             ^\n  159 |     expect(dlm?.invoice_no).toContain('360DLM');\n  160 |     // Both contain a FY-style string YY-YY\n  161 |     [pms, dlm].forEach(r => {\n    at /var/www/360lm/tests/sales_billing.spec.js:158:29"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:23.222Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-80868--no-is-FY-aware-per-company-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 29,
                        "line": 158
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-8d93caf851cc97f0f339",
              "file": "sales_billing.spec.js",
              "line": 154,
              "column": 3
            },
            {
              "title": "issued invoice has issued_at stamped",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1678,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:169:16",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 16,
                          "line": 169
                        },
                        "snippet": "  167 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,issued_at,paid_at');\n  168 |     const inv = rows[0];\n> 169 |     expect(inv.status).toBe('issued');\n      |                ^\n  170 |     expect(inv.issued_at).toBeTruthy();\n  171 |     expect(inv.paid_at).toBeNull();\n  172 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 169
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  167 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,issued_at,paid_at');\n  168 |     const inv = rows[0];\n> 169 |     expect(inv.status).toBe('issued');\n      |                ^\n  170 |     expect(inv.issued_at).toBeTruthy();\n  171 |     expect(inv.paid_at).toBeNull();\n  172 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:169:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:24.200Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e2c-voice-has-issued-at-stamped-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 16,
                        "line": 169
                      }
                    },
                    {
                      "workerIndex": 1679,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:169:16",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 16,
                          "line": 169
                        },
                        "snippet": "  167 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,issued_at,paid_at');\n  168 |     const inv = rows[0];\n> 169 |     expect(inv.status).toBe('issued');\n      |                ^\n  170 |     expect(inv.issued_at).toBeTruthy();\n  171 |     expect(inv.paid_at).toBeNull();\n  172 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 169
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  167 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,issued_at,paid_at');\n  168 |     const inv = rows[0];\n> 169 |     expect(inv.status).toBe('issued');\n      |                ^\n  170 |     expect(inv.issued_at).toBeTruthy();\n  171 |     expect(inv.paid_at).toBeNull();\n  172 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:169:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:25.140Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e2c-voice-has-issued-at-stamped-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 16,
                        "line": 169
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-5bacc7858b1d79b6bec7",
              "file": "sales_billing.spec.js",
              "line": 166,
              "column": 3
            },
            {
              "title": "paid invoice has both issued_at and paid_at",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1680,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:177:16",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 16,
                          "line": 177
                        },
                        "snippet": "  175 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-3&select=status,issued_at,paid_at');\n  176 |     const inv = rows[0];\n> 177 |     expect(inv.status).toBe('paid');\n      |                ^\n  178 |     expect(inv.issued_at).toBeTruthy();\n  179 |     expect(inv.paid_at).toBeTruthy();\n  180 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 177
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  175 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-3&select=status,issued_at,paid_at');\n  176 |     const inv = rows[0];\n> 177 |     expect(inv.status).toBe('paid');\n      |                ^\n  178 |     expect(inv.issued_at).toBeTruthy();\n  179 |     expect(inv.paid_at).toBeTruthy();\n  180 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:177:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:26.056Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-aa91b--both-issued-at-and-paid-at-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 16,
                        "line": 177
                      }
                    },
                    {
                      "workerIndex": 1681,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:177:16",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 16,
                          "line": 177
                        },
                        "snippet": "  175 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-3&select=status,issued_at,paid_at');\n  176 |     const inv = rows[0];\n> 177 |     expect(inv.status).toBe('paid');\n      |                ^\n  178 |     expect(inv.issued_at).toBeTruthy();\n  179 |     expect(inv.paid_at).toBeTruthy();\n  180 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 177
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  175 |     const rows = await dbGet('/invoices?invoice_id=eq.bt-inv-3&select=status,issued_at,paid_at');\n  176 |     const inv = rows[0];\n> 177 |     expect(inv.status).toBe('paid');\n      |                ^\n  178 |     expect(inv.issued_at).toBeTruthy();\n  179 |     expect(inv.paid_at).toBeTruthy();\n  180 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:177:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:27.034Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-aa91b--both-issued-at-and-paid-at-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 16,
                        "line": 177
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-c9a5bf4756443a758e34",
              "file": "sales_billing.spec.js",
              "line": 174,
              "column": 3
            },
            {
              "title": "invoice KPI reflects billing test data",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1682,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 86,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:185:41",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 41,
                          "line": 185
                        },
                        "snippet": "  183 |     const rows = await dbGet('/invoice_kpi');\n  184 |     const kpi = rows[0];\n> 185 |     expect(Number(kpi.invoices_issued)).toBeGreaterThanOrEqual(2); // bt-inv-2 + bt-inv-3\n      |                                         ^\n  186 |     expect(Number(kpi.billed_total)).toBeGreaterThan(0);\n  187 |     expect(Number(kpi.collected_total)).toBeGreaterThan(0);\n  188 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 41,
                            "line": 185
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  183 |     const rows = await dbGet('/invoice_kpi');\n  184 |     const kpi = rows[0];\n> 185 |     expect(Number(kpi.invoices_issued)).toBeGreaterThanOrEqual(2); // bt-inv-2 + bt-inv-3\n      |                                         ^\n  186 |     expect(Number(kpi.billed_total)).toBeGreaterThan(0);\n  187 |     expect(Number(kpi.collected_total)).toBeGreaterThan(0);\n  188 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:185:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:28.007Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f95b5--reflects-billing-test-data-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 41,
                        "line": 185
                      }
                    },
                    {
                      "workerIndex": 1683,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 65,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:185:41",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 41,
                          "line": 185
                        },
                        "snippet": "  183 |     const rows = await dbGet('/invoice_kpi');\n  184 |     const kpi = rows[0];\n> 185 |     expect(Number(kpi.invoices_issued)).toBeGreaterThanOrEqual(2); // bt-inv-2 + bt-inv-3\n      |                                         ^\n  186 |     expect(Number(kpi.billed_total)).toBeGreaterThan(0);\n  187 |     expect(Number(kpi.collected_total)).toBeGreaterThan(0);\n  188 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 41,
                            "line": 185
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThanOrEqual\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: >= \u001b[32m2\u001b[39m\nReceived:    \u001b[31m0\u001b[39m\n\n  183 |     const rows = await dbGet('/invoice_kpi');\n  184 |     const kpi = rows[0];\n> 185 |     expect(Number(kpi.invoices_issued)).toBeGreaterThanOrEqual(2); // bt-inv-2 + bt-inv-3\n      |                                         ^\n  186 |     expect(Number(kpi.billed_total)).toBeGreaterThan(0);\n  187 |     expect(Number(kpi.collected_total)).toBeGreaterThan(0);\n  188 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:185:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:29.033Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f95b5--reflects-billing-test-data-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 41,
                        "line": 185
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-896862aa52aa7869acde",
              "file": "sales_billing.spec.js",
              "line": 182,
              "column": 3
            },
            {
              "title": "Invoices tab button present in Job Detail HTML",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1684,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:193:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 193
                        },
                        "snippet": "  191 |     const ctx = await request.newContext();\n  192 |     const html = await (await ctx.get(BASE_URL)).text();\n> 193 |     expect(html).toContain('renderInvoicesTab()');\n      |                  ^\n  194 |     expect(html).toContain('id=\"tab-invoices\"');\n  195 |     expect(html).toContain('id=\"inv-form-title\"');\n  196 |     expect(html).toContain('id=\"inv-lines-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 193
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  191 |     const ctx = await request.newContext();\n  192 |     const html = await (await ctx.get(BASE_URL)).text();\n> 193 |     expect(html).toContain('renderInvoicesTab()');\n      |                  ^\n  194 |     expect(html).toContain('id=\"tab-invoices\"');\n  195 |     expect(html).toContain('id=\"inv-form-title\"');\n  196 |     expect(html).toContain('id=\"inv-lines-container\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:193:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:30.030Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-432ce--present-in-Job-Detail-HTML-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 193
                      }
                    },
                    {
                      "workerIndex": 1685,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 76,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:193:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 193
                        },
                        "snippet": "  191 |     const ctx = await request.newContext();\n  192 |     const html = await (await ctx.get(BASE_URL)).text();\n> 193 |     expect(html).toContain('renderInvoicesTab()');\n      |                  ^\n  194 |     expect(html).toContain('id=\"tab-invoices\"');\n  195 |     expect(html).toContain('id=\"inv-form-title\"');\n  196 |     expect(html).toContain('id=\"inv-lines-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 193
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderInvoicesTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  191 |     const ctx = await request.newContext();\n  192 |     const html = await (await ctx.get(BASE_URL)).text();\n> 193 |     expect(html).toContain('renderInvoicesTab()');\n      |                  ^\n  194 |     expect(html).toContain('id=\"tab-invoices\"');\n  195 |     expect(html).toContain('id=\"inv-form-title\"');\n  196 |     expect(html).toContain('id=\"inv-lines-container\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:193:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:31.065Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-432ce--present-in-Job-Detail-HTML-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 193
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-f312dfb15ea98f8d9a55",
              "file": "sales_billing.spec.js",
              "line": 190,
              "column": 3
            },
            {
              "title": "buildTaxInvoiceHtml exists in Sales JS",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1686,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:203:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 203
                        },
                        "snippet": "  201 |     const ctx = await request.newContext();\n  202 |     const html = await (await ctx.get(BASE_URL)).text();\n> 203 |     expect(html).toContain('function buildTaxInvoiceHtml');\n      |                  ^\n  204 |     expect(html).toContain('ORIGINAL FOR RECIPIENT');\n  205 |     expect(html).toContain('amountInWords');\n  206 |     expect(html).toContain('GST Rule 55'); // in challan builder nearby"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 203
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  201 |     const ctx = await request.newContext();\n  202 |     const html = await (await ctx.get(BASE_URL)).text();\n> 203 |     expect(html).toContain('function buildTaxInvoiceHtml');\n      |                  ^\n  204 |     expect(html).toContain('ORIGINAL FOR RECIPIENT');\n  205 |     expect(html).toContain('amountInWords');\n  206 |     expect(html).toContain('GST Rule 55'); // in challan builder nearby\n    at /var/www/360lm/tests/sales_billing.spec.js:203:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:32.144Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-1d72e-oiceHtml-exists-in-Sales-JS-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 203
                      }
                    },
                    {
                      "workerIndex": 1687,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 76,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:203:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 203
                        },
                        "snippet": "  201 |     const ctx = await request.newContext();\n  202 |     const html = await (await ctx.get(BASE_URL)).text();\n> 203 |     expect(html).toContain('function buildTaxInvoiceHtml');\n      |                  ^\n  204 |     expect(html).toContain('ORIGINAL FOR RECIPIENT');\n  205 |     expect(html).toContain('amountInWords');\n  206 |     expect(html).toContain('GST Rule 55'); // in challan builder nearby"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 203
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function buildTaxInvoiceHtml\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  201 |     const ctx = await request.newContext();\n  202 |     const html = await (await ctx.get(BASE_URL)).text();\n> 203 |     expect(html).toContain('function buildTaxInvoiceHtml');\n      |                  ^\n  204 |     expect(html).toContain('ORIGINAL FOR RECIPIENT');\n  205 |     expect(html).toContain('amountInWords');\n  206 |     expect(html).toContain('GST Rule 55'); // in challan builder nearby\n    at /var/www/360lm/tests/sales_billing.spec.js:203:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:33.129Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-1d72e-oiceHtml-exists-in-Sales-JS-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 203
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-5a606089e4d5682238bb",
              "file": "sales_billing.spec.js",
              "line": 200,
              "column": 3
            },
            {
              "title": "invoice tab visible in Job Detail screen (rendered UI)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 214,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1688,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:34.105Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 214,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-4fb5348960974fa600c2",
              "file": "sales_billing.spec.js",
              "line": 214,
              "column": 8
            }
          ]
        },
        {
          "title": "Sales Billing — Stage F: Challans",
          "file": "sales_billing.spec.js",
          "line": 234,
          "column": 6,
          "specs": [
            {
              "title": "three test challans exist with correct statuses",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 680,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 75,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:237:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 237
                        },
                        "snippet": "  235 |   test('three test challans exist with correct statuses', async () => {\n  236 |     const rows = await dbGet('/challans?challan_id=in.(bt-dc-1,bt-dc-2,bt-dc-3)&select=challan_id,challan_no,status,invoice_id,company&order=challan_id');\n> 237 |     expect(rows.length).toBe(3);\n      |                         ^\n  238 |     const byId = Object.fromEntries(rows.map(r => [r.challan_id, r]));\n  239 |     expect(byId['bt-dc-1'].status).toBe('open');\n  240 |     expect(byId['bt-dc-2'].status).toBe('delivered');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 237
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  235 |   test('three test challans exist with correct statuses', async () => {\n  236 |     const rows = await dbGet('/challans?challan_id=in.(bt-dc-1,bt-dc-2,bt-dc-3)&select=challan_id,challan_no,status,invoice_id,company&order=challan_id');\n> 237 |     expect(rows.length).toBe(3);\n      |                         ^\n  238 |     const byId = Object.fromEntries(rows.map(r => [r.challan_id, r]));\n  239 |     expect(byId['bt-dc-1'].status).toBe('open');\n  240 |     expect(byId['bt-dc-2'].status).toBe('delivered');\n    at /var/www/360lm/tests/sales_billing.spec.js:237:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:31.183Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-0974a-exist-with-correct-statuses-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 237
                      }
                    },
                    {
                      "workerIndex": 681,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:237:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 237
                        },
                        "snippet": "  235 |   test('three test challans exist with correct statuses', async () => {\n  236 |     const rows = await dbGet('/challans?challan_id=in.(bt-dc-1,bt-dc-2,bt-dc-3)&select=challan_id,challan_no,status,invoice_id,company&order=challan_id');\n> 237 |     expect(rows.length).toBe(3);\n      |                         ^\n  238 |     const byId = Object.fromEntries(rows.map(r => [r.challan_id, r]));\n  239 |     expect(byId['bt-dc-1'].status).toBe('open');\n  240 |     expect(byId['bt-dc-2'].status).toBe('delivered');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 237
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  235 |   test('three test challans exist with correct statuses', async () => {\n  236 |     const rows = await dbGet('/challans?challan_id=in.(bt-dc-1,bt-dc-2,bt-dc-3)&select=challan_id,challan_no,status,invoice_id,company&order=challan_id');\n> 237 |     expect(rows.length).toBe(3);\n      |                         ^\n  238 |     const byId = Object.fromEntries(rows.map(r => [r.challan_id, r]));\n  239 |     expect(byId['bt-dc-1'].status).toBe('open');\n  240 |     expect(byId['bt-dc-2'].status).toBe('delivered');\n    at /var/www/360lm/tests/sales_billing.spec.js:237:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:32.178Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-0974a-exist-with-correct-statuses-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 237
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-d1479c0dab6d0d537a9d",
              "file": "sales_billing.spec.js",
              "line": 235,
              "column": 3
            },
            {
              "title": "delivered challan has delivered_at stamped",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 682,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:250:20",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 20,
                          "line": 250
                        },
                        "snippet": "  248 |   test('delivered challan has delivered_at stamped', async () => {\n  249 |     const rows = await dbGet('/challans?challan_id=eq.bt-dc-2&select=status,delivered_at');\n> 250 |     expect(rows[0].status).toBe('delivered');\n      |                    ^\n  251 |     expect(rows[0].delivered_at).toBeTruthy();\n  252 |   });\n  253 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 20,
                            "line": 250
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  248 |   test('delivered challan has delivered_at stamped', async () => {\n  249 |     const rows = await dbGet('/challans?challan_id=eq.bt-dc-2&select=status,delivered_at');\n> 250 |     expect(rows[0].status).toBe('delivered');\n      |                    ^\n  251 |     expect(rows[0].delivered_at).toBeTruthy();\n  252 |   });\n  253 |\n    at /var/www/360lm/tests/sales_billing.spec.js:250:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:33.143Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-17a7b-an-has-delivered-at-stamped-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 20,
                        "line": 250
                      }
                    },
                    {
                      "workerIndex": 683,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:250:20",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 20,
                          "line": 250
                        },
                        "snippet": "  248 |   test('delivered challan has delivered_at stamped', async () => {\n  249 |     const rows = await dbGet('/challans?challan_id=eq.bt-dc-2&select=status,delivered_at');\n> 250 |     expect(rows[0].status).toBe('delivered');\n      |                    ^\n  251 |     expect(rows[0].delivered_at).toBeTruthy();\n  252 |   });\n  253 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 20,
                            "line": 250
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  248 |   test('delivered challan has delivered_at stamped', async () => {\n  249 |     const rows = await dbGet('/challans?challan_id=eq.bt-dc-2&select=status,delivered_at');\n> 250 |     expect(rows[0].status).toBe('delivered');\n      |                    ^\n  251 |     expect(rows[0].delivered_at).toBeTruthy();\n  252 |   });\n  253 |\n    at /var/www/360lm/tests/sales_billing.spec.js:250:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:34.135Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-17a7b-an-has-delivered-at-stamped-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 20,
                        "line": 250
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-b10c3c51b0d2b09b1b1a",
              "file": "sales_billing.spec.js",
              "line": 248,
              "column": 3
            },
            {
              "title": "challan lines carry HSN/SAC + unit",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 684,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 85,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:256:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 256
                        },
                        "snippet": "  254 |   test('challan lines carry HSN/SAC + unit', async () => {\n  255 |     const rows = await dbGet('/challan_lines?challan_id=eq.bt-dc-1&select=element,hsn_sac,unit,qty&order=sr_no');\n> 256 |     expect(rows.length).toBe(3);\n      |                         ^\n  257 |     rows.forEach(r => {\n  258 |       expect(r.hsn_sac).toBeTruthy();\n  259 |       expect(r.unit).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 256
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  254 |   test('challan lines carry HSN/SAC + unit', async () => {\n  255 |     const rows = await dbGet('/challan_lines?challan_id=eq.bt-dc-1&select=element,hsn_sac,unit,qty&order=sr_no');\n> 256 |     expect(rows.length).toBe(3);\n      |                         ^\n  257 |     rows.forEach(r => {\n  258 |       expect(r.hsn_sac).toBeTruthy();\n  259 |       expect(r.unit).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:256:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:35.056Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-d2de6-an-lines-carry-HSN-SAC-unit-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 256
                      }
                    },
                    {
                      "workerIndex": 685,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:256:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 256
                        },
                        "snippet": "  254 |   test('challan lines carry HSN/SAC + unit', async () => {\n  255 |     const rows = await dbGet('/challan_lines?challan_id=eq.bt-dc-1&select=element,hsn_sac,unit,qty&order=sr_no');\n> 256 |     expect(rows.length).toBe(3);\n      |                         ^\n  257 |     rows.forEach(r => {\n  258 |       expect(r.hsn_sac).toBeTruthy();\n  259 |       expect(r.unit).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 256
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  254 |   test('challan lines carry HSN/SAC + unit', async () => {\n  255 |     const rows = await dbGet('/challan_lines?challan_id=eq.bt-dc-1&select=element,hsn_sac,unit,qty&order=sr_no');\n> 256 |     expect(rows.length).toBe(3);\n      |                         ^\n  257 |     rows.forEach(r => {\n  258 |       expect(r.hsn_sac).toBeTruthy();\n  259 |       expect(r.unit).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:256:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:36.024Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-d2de6-an-lines-carry-HSN-SAC-unit-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 256
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-cc365ac0eb13382d269f",
              "file": "sales_billing.spec.js",
              "line": 254,
              "column": 3
            },
            {
              "title": "challans_v view enriches with job_name + invoice_no",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 686,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 72,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'job_name')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'job_name')\n    at /var/www/360lm/tests/sales_billing.spec.js:267:15",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 15,
                          "line": 267
                        },
                        "snippet": "  265 |     const rows = await dbGet('/challans_v?challan_id=eq.bt-dc-2&select=challan_no,status,job_name,invoice_no,line_count');\n  266 |     const dc = rows[0];\n> 267 |     expect(dc.job_name).toBeTruthy();\n      |               ^\n  268 |     expect(dc.invoice_no).toBeTruthy();\n  269 |     expect(Number(dc.line_count)).toBe(2);\n  270 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 15,
                            "line": 267
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'job_name')\n\n  265 |     const rows = await dbGet('/challans_v?challan_id=eq.bt-dc-2&select=challan_no,status,job_name,invoice_no,line_count');\n  266 |     const dc = rows[0];\n> 267 |     expect(dc.job_name).toBeTruthy();\n      |               ^\n  268 |     expect(dc.invoice_no).toBeTruthy();\n  269 |     expect(Number(dc.line_count)).toBe(2);\n  270 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:267:15"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:37.030Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-2c922-es-with-job-name-invoice-no-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 15,
                        "line": 267
                      }
                    },
                    {
                      "workerIndex": 687,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 68,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'job_name')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'job_name')\n    at /var/www/360lm/tests/sales_billing.spec.js:267:15",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 15,
                          "line": 267
                        },
                        "snippet": "  265 |     const rows = await dbGet('/challans_v?challan_id=eq.bt-dc-2&select=challan_no,status,job_name,invoice_no,line_count');\n  266 |     const dc = rows[0];\n> 267 |     expect(dc.job_name).toBeTruthy();\n      |               ^\n  268 |     expect(dc.invoice_no).toBeTruthy();\n  269 |     expect(Number(dc.line_count)).toBe(2);\n  270 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 15,
                            "line": 267
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'job_name')\n\n  265 |     const rows = await dbGet('/challans_v?challan_id=eq.bt-dc-2&select=challan_no,status,job_name,invoice_no,line_count');\n  266 |     const dc = rows[0];\n> 267 |     expect(dc.job_name).toBeTruthy();\n      |               ^\n  268 |     expect(dc.invoice_no).toBeTruthy();\n  269 |     expect(Number(dc.line_count)).toBe(2);\n  270 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:267:15"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:38.016Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-2c922-es-with-job-name-invoice-no-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 15,
                        "line": 267
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-dea92ee2b8213e4e643b",
              "file": "sales_billing.spec.js",
              "line": 264,
              "column": 3
            },
            {
              "title": "next_dc_no function generates DC-{COMPANY}/YY-YY/NNN format",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 688,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 91,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:38.980Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-6f151884189ed9a95289",
              "file": "sales_billing.spec.js",
              "line": 272,
              "column": 3
            },
            {
              "title": "Challans tab button present in Job Detail HTML",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 688,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 24,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:287:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 287
                        },
                        "snippet": "  285 |     const ctx = await request.newContext();\n  286 |     const html = await (await ctx.get(BASE_URL)).text();\n> 287 |     expect(html).toContain('renderChallansTab()');\n      |                  ^\n  288 |     expect(html).toContain('id=\"tab-challans\"');\n  289 |     expect(html).toContain('id=\"dc-form-title\"');\n  290 |     expect(html).toContain('id=\"dc-lines-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 287
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  285 |     const ctx = await request.newContext();\n  286 |     const html = await (await ctx.get(BASE_URL)).text();\n> 287 |     expect(html).toContain('renderChallansTab()');\n      |                  ^\n  288 |     expect(html).toContain('id=\"tab-challans\"');\n  289 |     expect(html).toContain('id=\"dc-form-title\"');\n  290 |     expect(html).toContain('id=\"dc-lines-container\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:287:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:39.130Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-4fa44--present-in-Job-Detail-HTML-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 287
                      }
                    },
                    {
                      "workerIndex": 689,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:287:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 287
                        },
                        "snippet": "  285 |     const ctx = await request.newContext();\n  286 |     const html = await (await ctx.get(BASE_URL)).text();\n> 287 |     expect(html).toContain('renderChallansTab()');\n      |                  ^\n  288 |     expect(html).toContain('id=\"tab-challans\"');\n  289 |     expect(html).toContain('id=\"dc-form-title\"');\n  290 |     expect(html).toContain('id=\"dc-lines-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 287
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  285 |     const ctx = await request.newContext();\n  286 |     const html = await (await ctx.get(BASE_URL)).text();\n> 287 |     expect(html).toContain('renderChallansTab()');\n      |                  ^\n  288 |     expect(html).toContain('id=\"tab-challans\"');\n  289 |     expect(html).toContain('id=\"dc-form-title\"');\n  290 |     expect(html).toContain('id=\"dc-lines-container\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:287:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:40.129Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-4fa44--present-in-Job-Detail-HTML-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 287
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-3cf4a6bb8dae82700e28",
              "file": "sales_billing.spec.js",
              "line": 284,
              "column": 3
            },
            {
              "title": "three test challans exist with correct statuses",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1688,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 68,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:237:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 237
                        },
                        "snippet": "  235 |   test('three test challans exist with correct statuses', async () => {\n  236 |     const rows = await dbGet('/challans?challan_id=in.(bt-dc-1,bt-dc-2,bt-dc-3)&select=challan_id,challan_no,status,invoice_id,company&order=challan_id');\n> 237 |     expect(rows.length).toBe(3);\n      |                         ^\n  238 |     const byId = Object.fromEntries(rows.map(r => [r.challan_id, r]));\n  239 |     expect(byId['bt-dc-1'].status).toBe('open');\n  240 |     expect(byId['bt-dc-2'].status).toBe('delivered');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 237
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  235 |   test('three test challans exist with correct statuses', async () => {\n  236 |     const rows = await dbGet('/challans?challan_id=in.(bt-dc-1,bt-dc-2,bt-dc-3)&select=challan_id,challan_no,status,invoice_id,company&order=challan_id');\n> 237 |     expect(rows.length).toBe(3);\n      |                         ^\n  238 |     const byId = Object.fromEntries(rows.map(r => [r.challan_id, r]));\n  239 |     expect(byId['bt-dc-1'].status).toBe('open');\n  240 |     expect(byId['bt-dc-2'].status).toBe('delivered');\n    at /var/www/360lm/tests/sales_billing.spec.js:237:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:34.106Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-0974a-exist-with-correct-statuses-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 237
                      }
                    },
                    {
                      "workerIndex": 1689,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 95,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:237:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 237
                        },
                        "snippet": "  235 |   test('three test challans exist with correct statuses', async () => {\n  236 |     const rows = await dbGet('/challans?challan_id=in.(bt-dc-1,bt-dc-2,bt-dc-3)&select=challan_id,challan_no,status,invoice_id,company&order=challan_id');\n> 237 |     expect(rows.length).toBe(3);\n      |                         ^\n  238 |     const byId = Object.fromEntries(rows.map(r => [r.challan_id, r]));\n  239 |     expect(byId['bt-dc-1'].status).toBe('open');\n  240 |     expect(byId['bt-dc-2'].status).toBe('delivered');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 237
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  235 |   test('three test challans exist with correct statuses', async () => {\n  236 |     const rows = await dbGet('/challans?challan_id=in.(bt-dc-1,bt-dc-2,bt-dc-3)&select=challan_id,challan_no,status,invoice_id,company&order=challan_id');\n> 237 |     expect(rows.length).toBe(3);\n      |                         ^\n  238 |     const byId = Object.fromEntries(rows.map(r => [r.challan_id, r]));\n  239 |     expect(byId['bt-dc-1'].status).toBe('open');\n  240 |     expect(byId['bt-dc-2'].status).toBe('delivered');\n    at /var/www/360lm/tests/sales_billing.spec.js:237:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:35.007Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-0974a-exist-with-correct-statuses-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 237
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-8bef41b31fb4c786c50d",
              "file": "sales_billing.spec.js",
              "line": 235,
              "column": 3
            },
            {
              "title": "delivered challan has delivered_at stamped",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1690,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:250:20",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 20,
                          "line": 250
                        },
                        "snippet": "  248 |   test('delivered challan has delivered_at stamped', async () => {\n  249 |     const rows = await dbGet('/challans?challan_id=eq.bt-dc-2&select=status,delivered_at');\n> 250 |     expect(rows[0].status).toBe('delivered');\n      |                    ^\n  251 |     expect(rows[0].delivered_at).toBeTruthy();\n  252 |   });\n  253 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 20,
                            "line": 250
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  248 |   test('delivered challan has delivered_at stamped', async () => {\n  249 |     const rows = await dbGet('/challans?challan_id=eq.bt-dc-2&select=status,delivered_at');\n> 250 |     expect(rows[0].status).toBe('delivered');\n      |                    ^\n  251 |     expect(rows[0].delivered_at).toBeTruthy();\n  252 |   });\n  253 |\n    at /var/www/360lm/tests/sales_billing.spec.js:250:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:35.976Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-17a7b-an-has-delivered-at-stamped-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 20,
                        "line": 250
                      }
                    },
                    {
                      "workerIndex": 1691,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 72,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'status')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'status')\n    at /var/www/360lm/tests/sales_billing.spec.js:250:20",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 20,
                          "line": 250
                        },
                        "snippet": "  248 |   test('delivered challan has delivered_at stamped', async () => {\n  249 |     const rows = await dbGet('/challans?challan_id=eq.bt-dc-2&select=status,delivered_at');\n> 250 |     expect(rows[0].status).toBe('delivered');\n      |                    ^\n  251 |     expect(rows[0].delivered_at).toBeTruthy();\n  252 |   });\n  253 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 20,
                            "line": 250
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'status')\n\n  248 |   test('delivered challan has delivered_at stamped', async () => {\n  249 |     const rows = await dbGet('/challans?challan_id=eq.bt-dc-2&select=status,delivered_at');\n> 250 |     expect(rows[0].status).toBe('delivered');\n      |                    ^\n  251 |     expect(rows[0].delivered_at).toBeTruthy();\n  252 |   });\n  253 |\n    at /var/www/360lm/tests/sales_billing.spec.js:250:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:36.991Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-17a7b-an-has-delivered-at-stamped-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 20,
                        "line": 250
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-c860c10ce83e328b08fb",
              "file": "sales_billing.spec.js",
              "line": 248,
              "column": 3
            },
            {
              "title": "challan lines carry HSN/SAC + unit",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1692,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 68,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:256:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 256
                        },
                        "snippet": "  254 |   test('challan lines carry HSN/SAC + unit', async () => {\n  255 |     const rows = await dbGet('/challan_lines?challan_id=eq.bt-dc-1&select=element,hsn_sac,unit,qty&order=sr_no');\n> 256 |     expect(rows.length).toBe(3);\n      |                         ^\n  257 |     rows.forEach(r => {\n  258 |       expect(r.hsn_sac).toBeTruthy();\n  259 |       expect(r.unit).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 256
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  254 |   test('challan lines carry HSN/SAC + unit', async () => {\n  255 |     const rows = await dbGet('/challan_lines?challan_id=eq.bt-dc-1&select=element,hsn_sac,unit,qty&order=sr_no');\n> 256 |     expect(rows.length).toBe(3);\n      |                         ^\n  257 |     rows.forEach(r => {\n  258 |       expect(r.hsn_sac).toBeTruthy();\n  259 |       expect(r.unit).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:256:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:38.058Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-d2de6-an-lines-carry-HSN-SAC-unit-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 256
                      }
                    },
                    {
                      "workerIndex": 1693,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 73,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:256:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 256
                        },
                        "snippet": "  254 |   test('challan lines carry HSN/SAC + unit', async () => {\n  255 |     const rows = await dbGet('/challan_lines?challan_id=eq.bt-dc-1&select=element,hsn_sac,unit,qty&order=sr_no');\n> 256 |     expect(rows.length).toBe(3);\n      |                         ^\n  257 |     rows.forEach(r => {\n  258 |       expect(r.hsn_sac).toBeTruthy();\n  259 |       expect(r.unit).toBeTruthy();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 256
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  254 |   test('challan lines carry HSN/SAC + unit', async () => {\n  255 |     const rows = await dbGet('/challan_lines?challan_id=eq.bt-dc-1&select=element,hsn_sac,unit,qty&order=sr_no');\n> 256 |     expect(rows.length).toBe(3);\n      |                         ^\n  257 |     rows.forEach(r => {\n  258 |       expect(r.hsn_sac).toBeTruthy();\n  259 |       expect(r.unit).toBeTruthy();\n    at /var/www/360lm/tests/sales_billing.spec.js:256:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:38.964Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-d2de6-an-lines-carry-HSN-SAC-unit-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 256
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-00bc5c2b0b6c975dc19b",
              "file": "sales_billing.spec.js",
              "line": 254,
              "column": 3
            },
            {
              "title": "challans_v view enriches with job_name + invoice_no",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1694,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'job_name')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'job_name')\n    at /var/www/360lm/tests/sales_billing.spec.js:267:15",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 15,
                          "line": 267
                        },
                        "snippet": "  265 |     const rows = await dbGet('/challans_v?challan_id=eq.bt-dc-2&select=challan_no,status,job_name,invoice_no,line_count');\n  266 |     const dc = rows[0];\n> 267 |     expect(dc.job_name).toBeTruthy();\n      |               ^\n  268 |     expect(dc.invoice_no).toBeTruthy();\n  269 |     expect(Number(dc.line_count)).toBe(2);\n  270 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 15,
                            "line": 267
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'job_name')\n\n  265 |     const rows = await dbGet('/challans_v?challan_id=eq.bt-dc-2&select=challan_no,status,job_name,invoice_no,line_count');\n  266 |     const dc = rows[0];\n> 267 |     expect(dc.job_name).toBeTruthy();\n      |               ^\n  268 |     expect(dc.invoice_no).toBeTruthy();\n  269 |     expect(Number(dc.line_count)).toBe(2);\n  270 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:267:15"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:39.883Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-2c922-es-with-job-name-invoice-no-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 15,
                        "line": 267
                      }
                    },
                    {
                      "workerIndex": 1695,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'job_name')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'job_name')\n    at /var/www/360lm/tests/sales_billing.spec.js:267:15",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 15,
                          "line": 267
                        },
                        "snippet": "  265 |     const rows = await dbGet('/challans_v?challan_id=eq.bt-dc-2&select=challan_no,status,job_name,invoice_no,line_count');\n  266 |     const dc = rows[0];\n> 267 |     expect(dc.job_name).toBeTruthy();\n      |               ^\n  268 |     expect(dc.invoice_no).toBeTruthy();\n  269 |     expect(Number(dc.line_count)).toBe(2);\n  270 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 15,
                            "line": 267
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'job_name')\n\n  265 |     const rows = await dbGet('/challans_v?challan_id=eq.bt-dc-2&select=challan_no,status,job_name,invoice_no,line_count');\n  266 |     const dc = rows[0];\n> 267 |     expect(dc.job_name).toBeTruthy();\n      |               ^\n  268 |     expect(dc.invoice_no).toBeTruthy();\n  269 |     expect(Number(dc.line_count)).toBe(2);\n  270 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:267:15"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:40.878Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-2c922-es-with-job-name-invoice-no-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 15,
                        "line": 267
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-ff99621f738ad0cb3bdf",
              "file": "sales_billing.spec.js",
              "line": 264,
              "column": 3
            },
            {
              "title": "next_dc_no function generates DC-{COMPANY}/YY-YY/NNN format",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1696,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 76,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:41.728Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-e857101f0cba566e4ad8",
              "file": "sales_billing.spec.js",
              "line": 272,
              "column": 3
            },
            {
              "title": "Challans tab button present in Job Detail HTML",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1696,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 17,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:287:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 287
                        },
                        "snippet": "  285 |     const ctx = await request.newContext();\n  286 |     const html = await (await ctx.get(BASE_URL)).text();\n> 287 |     expect(html).toContain('renderChallansTab()');\n      |                  ^\n  288 |     expect(html).toContain('id=\"tab-challans\"');\n  289 |     expect(html).toContain('id=\"dc-form-title\"');\n  290 |     expect(html).toContain('id=\"dc-lines-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 287
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  285 |     const ctx = await request.newContext();\n  286 |     const html = await (await ctx.get(BASE_URL)).text();\n> 287 |     expect(html).toContain('renderChallansTab()');\n      |                  ^\n  288 |     expect(html).toContain('id=\"tab-challans\"');\n  289 |     expect(html).toContain('id=\"dc-form-title\"');\n  290 |     expect(html).toContain('id=\"dc-lines-container\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:287:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:41.849Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-4fa44--present-in-Job-Detail-HTML-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 287
                      }
                    },
                    {
                      "workerIndex": 1697,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:287:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 287
                        },
                        "snippet": "  285 |     const ctx = await request.newContext();\n  286 |     const html = await (await ctx.get(BASE_URL)).text();\n> 287 |     expect(html).toContain('renderChallansTab()');\n      |                  ^\n  288 |     expect(html).toContain('id=\"tab-challans\"');\n  289 |     expect(html).toContain('id=\"dc-form-title\"');\n  290 |     expect(html).toContain('id=\"dc-lines-container\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 287
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"renderChallansTab()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  285 |     const ctx = await request.newContext();\n  286 |     const html = await (await ctx.get(BASE_URL)).text();\n> 287 |     expect(html).toContain('renderChallansTab()');\n      |                  ^\n  288 |     expect(html).toContain('id=\"tab-challans\"');\n  289 |     expect(html).toContain('id=\"dc-form-title\"');\n  290 |     expect(html).toContain('id=\"dc-lines-container\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:287:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:42.799Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-4fa44--present-in-Job-Detail-HTML-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 287
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-240ca32fb39112171a1c",
              "file": "sales_billing.spec.js",
              "line": 284,
              "column": 3
            }
          ]
        },
        {
          "title": "Sales Billing — Stage G: Payments",
          "file": "sales_billing.spec.js",
          "line": 299,
          "column": 6,
          "specs": [
            {
              "title": "invoice_receipts exist for bt-inv-2 and bt-inv-3",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 690,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 75,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:302:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 302
                        },
                        "snippet": "  300 |   test('invoice_receipts exist for bt-inv-2 and bt-inv-3', async () => {\n  301 |     const rows = await dbGet('/invoice_receipts?invoice_id=in.(bt-inv-2,bt-inv-3)&select=invoice_id,amount,payment_mode,receipt_id&order=invoice_id,payment_date');\n> 302 |     expect(rows.length).toBe(3); // 1 for inv-2, 2 for inv-3\n      |                         ^\n  303 |     const inv2 = rows.filter(r => r.invoice_id === 'bt-inv-2');\n  304 |     const inv3 = rows.filter(r => r.invoice_id === 'bt-inv-3');\n  305 |     expect(inv2.length).toBe(1);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 302
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  300 |   test('invoice_receipts exist for bt-inv-2 and bt-inv-3', async () => {\n  301 |     const rows = await dbGet('/invoice_receipts?invoice_id=in.(bt-inv-2,bt-inv-3)&select=invoice_id,amount,payment_mode,receipt_id&order=invoice_id,payment_date');\n> 302 |     expect(rows.length).toBe(3); // 1 for inv-2, 2 for inv-3\n      |                         ^\n  303 |     const inv2 = rows.filter(r => r.invoice_id === 'bt-inv-2');\n  304 |     const inv3 = rows.filter(r => r.invoice_id === 'bt-inv-3');\n  305 |     expect(inv2.length).toBe(1);\n    at /var/www/360lm/tests/sales_billing.spec.js:302:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:41.115Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-84fcd-t-for-bt-inv-2-and-bt-inv-3-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 302
                      }
                    },
                    {
                      "workerIndex": 691,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:302:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 302
                        },
                        "snippet": "  300 |   test('invoice_receipts exist for bt-inv-2 and bt-inv-3', async () => {\n  301 |     const rows = await dbGet('/invoice_receipts?invoice_id=in.(bt-inv-2,bt-inv-3)&select=invoice_id,amount,payment_mode,receipt_id&order=invoice_id,payment_date');\n> 302 |     expect(rows.length).toBe(3); // 1 for inv-2, 2 for inv-3\n      |                         ^\n  303 |     const inv2 = rows.filter(r => r.invoice_id === 'bt-inv-2');\n  304 |     const inv3 = rows.filter(r => r.invoice_id === 'bt-inv-3');\n  305 |     expect(inv2.length).toBe(1);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 302
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  300 |   test('invoice_receipts exist for bt-inv-2 and bt-inv-3', async () => {\n  301 |     const rows = await dbGet('/invoice_receipts?invoice_id=in.(bt-inv-2,bt-inv-3)&select=invoice_id,amount,payment_mode,receipt_id&order=invoice_id,payment_date');\n> 302 |     expect(rows.length).toBe(3); // 1 for inv-2, 2 for inv-3\n      |                         ^\n  303 |     const inv2 = rows.filter(r => r.invoice_id === 'bt-inv-2');\n  304 |     const inv3 = rows.filter(r => r.invoice_id === 'bt-inv-3');\n  305 |     expect(inv2.length).toBe(1);\n    at /var/www/360lm/tests/sales_billing.spec.js:302:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:42.098Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-84fcd-t-for-bt-inv-2-and-bt-inv-3-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 302
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-c3e7c7db489c059d6bec",
              "file": "sales_billing.spec.js",
              "line": 300,
              "column": 3
            },
            {
              "title": "invoice_payment_summary shows correct paid_amount and balance_due",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 692,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n    at /var/www/360lm/tests/sales_billing.spec.js:317:36",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 36,
                          "line": 317
                        },
                        "snippet": "  315 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  316 |     // Invoice 2: partial — 107085 paid of 214170\n> 317 |     expect(Number(byId['bt-inv-2'].paid_amount)).toBeCloseTo(107085, 0);\n      |                                    ^\n  318 |     expect(Number(byId['bt-inv-2'].balance_due)).toBeCloseTo(107085, 0);\n  319 |     expect(byId['bt-inv-2'].status).toBe('issued');\n  320 |     // Invoice 3: fully paid"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 36,
                            "line": 317
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n\n  315 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  316 |     // Invoice 2: partial — 107085 paid of 214170\n> 317 |     expect(Number(byId['bt-inv-2'].paid_amount)).toBeCloseTo(107085, 0);\n      |                                    ^\n  318 |     expect(Number(byId['bt-inv-2'].balance_due)).toBeCloseTo(107085, 0);\n  319 |     expect(byId['bt-inv-2'].status).toBe('issued');\n  320 |     // Invoice 3: fully paid\n    at /var/www/360lm/tests/sales_billing.spec.js:317:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:43.007Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-aede4-paid-amount-and-balance-due-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 36,
                        "line": 317
                      }
                    },
                    {
                      "workerIndex": 693,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n    at /var/www/360lm/tests/sales_billing.spec.js:317:36",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 36,
                          "line": 317
                        },
                        "snippet": "  315 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  316 |     // Invoice 2: partial — 107085 paid of 214170\n> 317 |     expect(Number(byId['bt-inv-2'].paid_amount)).toBeCloseTo(107085, 0);\n      |                                    ^\n  318 |     expect(Number(byId['bt-inv-2'].balance_due)).toBeCloseTo(107085, 0);\n  319 |     expect(byId['bt-inv-2'].status).toBe('issued');\n  320 |     // Invoice 3: fully paid"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 36,
                            "line": 317
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n\n  315 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  316 |     // Invoice 2: partial — 107085 paid of 214170\n> 317 |     expect(Number(byId['bt-inv-2'].paid_amount)).toBeCloseTo(107085, 0);\n      |                                    ^\n  318 |     expect(Number(byId['bt-inv-2'].balance_due)).toBeCloseTo(107085, 0);\n  319 |     expect(byId['bt-inv-2'].status).toBe('issued');\n  320 |     // Invoice 3: fully paid\n    at /var/www/360lm/tests/sales_billing.spec.js:317:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:43.889Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-aede4-paid-amount-and-balance-due-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 36,
                        "line": 317
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-e561eefd6ff3aa3e9e25",
              "file": "sales_billing.spec.js",
              "line": 313,
              "column": 3
            },
            {
              "title": "auto-paid: new receipt that covers balance flips invoice to paid",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 694,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 77,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:337:20",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 20,
                          "line": 337
                        },
                        "snippet": "  335 |       created_by:   'pw-test',\n  336 |     });\n> 337 |     expect(status).toBe(201);\n      |                    ^\n  338 |\n  339 |     // Trigger auto-paid check\n  340 |     const invRows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,paid_at');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 20,
                            "line": 337
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m\n\n  335 |       created_by:   'pw-test',\n  336 |     });\n> 337 |     expect(status).toBe(201);\n      |                    ^\n  338 |\n  339 |     // Trigger auto-paid check\n  340 |     const invRows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,paid_at');\n    at /var/www/360lm/tests/sales_billing.spec.js:337:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:44.780Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-71b2a-lance-flips-invoice-to-paid-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 20,
                        "line": 337
                      }
                    },
                    {
                      "workerIndex": 695,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:337:20",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 20,
                          "line": 337
                        },
                        "snippet": "  335 |       created_by:   'pw-test',\n  336 |     });\n> 337 |     expect(status).toBe(201);\n      |                    ^\n  338 |\n  339 |     // Trigger auto-paid check\n  340 |     const invRows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,paid_at');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 20,
                            "line": 337
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m\n\n  335 |       created_by:   'pw-test',\n  336 |     });\n> 337 |     expect(status).toBe(201);\n      |                    ^\n  338 |\n  339 |     // Trigger auto-paid check\n  340 |     const invRows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,paid_at');\n    at /var/www/360lm/tests/sales_billing.spec.js:337:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:45.660Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-71b2a-lance-flips-invoice-to-paid-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 20,
                        "line": 337
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-571809ded6a7b853d314",
              "file": "sales_billing.spec.js",
              "line": 325,
              "column": 3
            },
            {
              "title": "invoice_payment_summary view updates after receipt insert",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 696,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'balance_due')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'balance_due')\n    at /var/www/360lm/tests/sales_billing.spec.js:352:40",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 40,
                          "line": 352
                        },
                        "snippet": "  350 |   test('invoice_payment_summary view updates after receipt insert', async () => {\n  351 |     const before = await dbGet('/invoice_payment_summary?invoice_id=eq.bt-inv-2');\n> 352 |     const balBefore = Number(before[0].balance_due);\n      |                                        ^\n  353 |     expect(balBefore).toBeGreaterThan(0);\n  354 |     // Add a small part-payment\n  355 |     const probeId = 'bt-partial-' + RUN_ID;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 40,
                            "line": 352
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'balance_due')\n\n  350 |   test('invoice_payment_summary view updates after receipt insert', async () => {\n  351 |     const before = await dbGet('/invoice_payment_summary?invoice_id=eq.bt-inv-2');\n> 352 |     const balBefore = Number(before[0].balance_due);\n      |                                        ^\n  353 |     expect(balBefore).toBeGreaterThan(0);\n  354 |     // Add a small part-payment\n  355 |     const probeId = 'bt-partial-' + RUN_ID;\n    at /var/www/360lm/tests/sales_billing.spec.js:352:40"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:46.614Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-a58f6-pdates-after-receipt-insert-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 40,
                        "line": 352
                      }
                    },
                    {
                      "workerIndex": 697,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 67,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'balance_due')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'balance_due')\n    at /var/www/360lm/tests/sales_billing.spec.js:352:40",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 40,
                          "line": 352
                        },
                        "snippet": "  350 |   test('invoice_payment_summary view updates after receipt insert', async () => {\n  351 |     const before = await dbGet('/invoice_payment_summary?invoice_id=eq.bt-inv-2');\n> 352 |     const balBefore = Number(before[0].balance_due);\n      |                                        ^\n  353 |     expect(balBefore).toBeGreaterThan(0);\n  354 |     // Add a small part-payment\n  355 |     const probeId = 'bt-partial-' + RUN_ID;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 40,
                            "line": 352
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'balance_due')\n\n  350 |   test('invoice_payment_summary view updates after receipt insert', async () => {\n  351 |     const before = await dbGet('/invoice_payment_summary?invoice_id=eq.bt-inv-2');\n> 352 |     const balBefore = Number(before[0].balance_due);\n      |                                        ^\n  353 |     expect(balBefore).toBeGreaterThan(0);\n  354 |     // Add a small part-payment\n  355 |     const probeId = 'bt-partial-' + RUN_ID;\n    at /var/www/360lm/tests/sales_billing.spec.js:352:40"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:47.556Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-a58f6-pdates-after-receipt-insert-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 40,
                        "line": 352
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-4a78ae2627186e07b098",
              "file": "sales_billing.spec.js",
              "line": 350,
              "column": 3
            },
            {
              "title": "payment modal present in HTML",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 698,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:369:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 369
                        },
                        "snippet": "  367 |     const ctx = await request.newContext();\n  368 |     const html = await (await ctx.get(BASE_URL)).text();\n> 369 |     expect(html).toContain('id=\"modal-payment\"');\n      |                  ^\n  370 |     expect(html).toContain('id=\"pay-amount\"');\n  371 |     expect(html).toContain('id=\"pay-mode\"');\n  372 |     expect(html).toContain('id=\"pay-ref\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 369
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  367 |     const ctx = await request.newContext();\n  368 |     const html = await (await ctx.get(BASE_URL)).text();\n> 369 |     expect(html).toContain('id=\"modal-payment\"');\n      |                  ^\n  370 |     expect(html).toContain('id=\"pay-amount\"');\n  371 |     expect(html).toContain('id=\"pay-mode\"');\n  372 |     expect(html).toContain('id=\"pay-ref\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:369:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:48.576Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-afa01-yment-modal-present-in-HTML-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 369
                      }
                    },
                    {
                      "workerIndex": 699,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:369:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 369
                        },
                        "snippet": "  367 |     const ctx = await request.newContext();\n  368 |     const html = await (await ctx.get(BASE_URL)).text();\n> 369 |     expect(html).toContain('id=\"modal-payment\"');\n      |                  ^\n  370 |     expect(html).toContain('id=\"pay-amount\"');\n  371 |     expect(html).toContain('id=\"pay-mode\"');\n  372 |     expect(html).toContain('id=\"pay-ref\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 369
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  367 |     const ctx = await request.newContext();\n  368 |     const html = await (await ctx.get(BASE_URL)).text();\n> 369 |     expect(html).toContain('id=\"modal-payment\"');\n      |                  ^\n  370 |     expect(html).toContain('id=\"pay-amount\"');\n  371 |     expect(html).toContain('id=\"pay-mode\"');\n  372 |     expect(html).toContain('id=\"pay-ref\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:369:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:49.554Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-afa01-yment-modal-present-in-HTML-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 369
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-d305efc466173a28c889",
              "file": "sales_billing.spec.js",
              "line": 366,
              "column": 3
            },
            {
              "title": "Custodian invoice-link fields present in custodian HTML",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 700,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:380:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 380
                        },
                        "snippet": "  378 |     const ctx = await request.newContext();\n  379 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 380 |     expect(html).toContain('id=\"tf-invoice-link-group\"');\n      |                  ^\n  381 |     expect(html).toContain('id=\"tf-invoice-link\"');\n  382 |     expect(html).toContain('loadPartyInvoices');\n  383 |     expect(html).toMatch(/custodian-v\\d+/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 380
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  378 |     const ctx = await request.newContext();\n  379 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 380 |     expect(html).toContain('id=\"tf-invoice-link-group\"');\n      |                  ^\n  381 |     expect(html).toContain('id=\"tf-invoice-link\"');\n  382 |     expect(html).toContain('loadPartyInvoices');\n  383 |     expect(html).toMatch(/custodian-v\\d+/);\n    at /var/www/360lm/tests/sales_billing.spec.js:380:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:50.423Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-be768-s-present-in-custodian-HTML-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 380
                      }
                    },
                    {
                      "workerIndex": 701,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:380:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 380
                        },
                        "snippet": "  378 |     const ctx = await request.newContext();\n  379 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 380 |     expect(html).toContain('id=\"tf-invoice-link-group\"');\n      |                  ^\n  381 |     expect(html).toContain('id=\"tf-invoice-link\"');\n  382 |     expect(html).toContain('loadPartyInvoices');\n  383 |     expect(html).toMatch(/custodian-v\\d+/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 380
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  378 |     const ctx = await request.newContext();\n  379 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 380 |     expect(html).toContain('id=\"tf-invoice-link-group\"');\n      |                  ^\n  381 |     expect(html).toContain('id=\"tf-invoice-link\"');\n  382 |     expect(html).toContain('loadPartyInvoices');\n  383 |     expect(html).toMatch(/custodian-v\\d+/);\n    at /var/www/360lm/tests/sales_billing.spec.js:380:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:51.375Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-be768-s-present-in-custodian-HTML-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 380
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-f0d76067f824e128e747",
              "file": "sales_billing.spec.js",
              "line": 377,
              "column": 3
            },
            {
              "title": "invoice_receipts exist for bt-inv-2 and bt-inv-3",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1698,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:302:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 302
                        },
                        "snippet": "  300 |   test('invoice_receipts exist for bt-inv-2 and bt-inv-3', async () => {\n  301 |     const rows = await dbGet('/invoice_receipts?invoice_id=in.(bt-inv-2,bt-inv-3)&select=invoice_id,amount,payment_mode,receipt_id&order=invoice_id,payment_date');\n> 302 |     expect(rows.length).toBe(3); // 1 for inv-2, 2 for inv-3\n      |                         ^\n  303 |     const inv2 = rows.filter(r => r.invoice_id === 'bt-inv-2');\n  304 |     const inv3 = rows.filter(r => r.invoice_id === 'bt-inv-3');\n  305 |     expect(inv2.length).toBe(1);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 302
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  300 |   test('invoice_receipts exist for bt-inv-2 and bt-inv-3', async () => {\n  301 |     const rows = await dbGet('/invoice_receipts?invoice_id=in.(bt-inv-2,bt-inv-3)&select=invoice_id,amount,payment_mode,receipt_id&order=invoice_id,payment_date');\n> 302 |     expect(rows.length).toBe(3); // 1 for inv-2, 2 for inv-3\n      |                         ^\n  303 |     const inv2 = rows.filter(r => r.invoice_id === 'bt-inv-2');\n  304 |     const inv3 = rows.filter(r => r.invoice_id === 'bt-inv-3');\n  305 |     expect(inv2.length).toBe(1);\n    at /var/www/360lm/tests/sales_billing.spec.js:302:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:43.735Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-84fcd-t-for-bt-inv-2-and-bt-inv-3-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 302
                      }
                    },
                    {
                      "workerIndex": 1699,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:302:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 302
                        },
                        "snippet": "  300 |   test('invoice_receipts exist for bt-inv-2 and bt-inv-3', async () => {\n  301 |     const rows = await dbGet('/invoice_receipts?invoice_id=in.(bt-inv-2,bt-inv-3)&select=invoice_id,amount,payment_mode,receipt_id&order=invoice_id,payment_date');\n> 302 |     expect(rows.length).toBe(3); // 1 for inv-2, 2 for inv-3\n      |                         ^\n  303 |     const inv2 = rows.filter(r => r.invoice_id === 'bt-inv-2');\n  304 |     const inv3 = rows.filter(r => r.invoice_id === 'bt-inv-3');\n  305 |     expect(inv2.length).toBe(1);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 302
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m3\u001b[39m\nReceived: \u001b[31m0\u001b[39m\n\n  300 |   test('invoice_receipts exist for bt-inv-2 and bt-inv-3', async () => {\n  301 |     const rows = await dbGet('/invoice_receipts?invoice_id=in.(bt-inv-2,bt-inv-3)&select=invoice_id,amount,payment_mode,receipt_id&order=invoice_id,payment_date');\n> 302 |     expect(rows.length).toBe(3); // 1 for inv-2, 2 for inv-3\n      |                         ^\n  303 |     const inv2 = rows.filter(r => r.invoice_id === 'bt-inv-2');\n  304 |     const inv3 = rows.filter(r => r.invoice_id === 'bt-inv-3');\n  305 |     expect(inv2.length).toBe(1);\n    at /var/www/360lm/tests/sales_billing.spec.js:302:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:44.566Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-84fcd-t-for-bt-inv-2-and-bt-inv-3-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 302
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-e04968d7229caaec0557",
              "file": "sales_billing.spec.js",
              "line": 300,
              "column": 3
            },
            {
              "title": "invoice_payment_summary shows correct paid_amount and balance_due",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1700,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n    at /var/www/360lm/tests/sales_billing.spec.js:317:36",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 36,
                          "line": 317
                        },
                        "snippet": "  315 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  316 |     // Invoice 2: partial — 107085 paid of 214170\n> 317 |     expect(Number(byId['bt-inv-2'].paid_amount)).toBeCloseTo(107085, 0);\n      |                                    ^\n  318 |     expect(Number(byId['bt-inv-2'].balance_due)).toBeCloseTo(107085, 0);\n  319 |     expect(byId['bt-inv-2'].status).toBe('issued');\n  320 |     // Invoice 3: fully paid"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 36,
                            "line": 317
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n\n  315 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  316 |     // Invoice 2: partial — 107085 paid of 214170\n> 317 |     expect(Number(byId['bt-inv-2'].paid_amount)).toBeCloseTo(107085, 0);\n      |                                    ^\n  318 |     expect(Number(byId['bt-inv-2'].balance_due)).toBeCloseTo(107085, 0);\n  319 |     expect(byId['bt-inv-2'].status).toBe('issued');\n  320 |     // Invoice 3: fully paid\n    at /var/www/360lm/tests/sales_billing.spec.js:317:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:45.453Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-aede4-paid-amount-and-balance-due-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 36,
                        "line": 317
                      }
                    },
                    {
                      "workerIndex": 1701,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n    at /var/www/360lm/tests/sales_billing.spec.js:317:36",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 36,
                          "line": 317
                        },
                        "snippet": "  315 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  316 |     // Invoice 2: partial — 107085 paid of 214170\n> 317 |     expect(Number(byId['bt-inv-2'].paid_amount)).toBeCloseTo(107085, 0);\n      |                                    ^\n  318 |     expect(Number(byId['bt-inv-2'].balance_due)).toBeCloseTo(107085, 0);\n  319 |     expect(byId['bt-inv-2'].status).toBe('issued');\n  320 |     // Invoice 3: fully paid"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 36,
                            "line": 317
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n\n  315 |     const byId = Object.fromEntries(rows.map(r => [r.invoice_id, r]));\n  316 |     // Invoice 2: partial — 107085 paid of 214170\n> 317 |     expect(Number(byId['bt-inv-2'].paid_amount)).toBeCloseTo(107085, 0);\n      |                                    ^\n  318 |     expect(Number(byId['bt-inv-2'].balance_due)).toBeCloseTo(107085, 0);\n  319 |     expect(byId['bt-inv-2'].status).toBe('issued');\n  320 |     // Invoice 3: fully paid\n    at /var/www/360lm/tests/sales_billing.spec.js:317:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:46.325Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-aede4-paid-amount-and-balance-due-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 36,
                        "line": 317
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-4f57e585d0d376f992f1",
              "file": "sales_billing.spec.js",
              "line": 313,
              "column": 3
            },
            {
              "title": "auto-paid: new receipt that covers balance flips invoice to paid",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1702,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 71,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:337:20",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 20,
                          "line": 337
                        },
                        "snippet": "  335 |       created_by:   'pw-test',\n  336 |     });\n> 337 |     expect(status).toBe(201);\n      |                    ^\n  338 |\n  339 |     // Trigger auto-paid check\n  340 |     const invRows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,paid_at');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 20,
                            "line": 337
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m\n\n  335 |       created_by:   'pw-test',\n  336 |     });\n> 337 |     expect(status).toBe(201);\n      |                    ^\n  338 |\n  339 |     // Trigger auto-paid check\n  340 |     const invRows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,paid_at');\n    at /var/www/360lm/tests/sales_billing.spec.js:337:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:47.240Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-71b2a-lance-flips-invoice-to-paid-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 20,
                        "line": 337
                      }
                    },
                    {
                      "workerIndex": 1703,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:337:20",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 20,
                          "line": 337
                        },
                        "snippet": "  335 |       created_by:   'pw-test',\n  336 |     });\n> 337 |     expect(status).toBe(201);\n      |                    ^\n  338 |\n  339 |     // Trigger auto-paid check\n  340 |     const invRows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,paid_at');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 20,
                            "line": 337
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m201\u001b[39m\nReceived: \u001b[31m409\u001b[39m\n\n  335 |       created_by:   'pw-test',\n  336 |     });\n> 337 |     expect(status).toBe(201);\n      |                    ^\n  338 |\n  339 |     // Trigger auto-paid check\n  340 |     const invRows = await dbGet('/invoices?invoice_id=eq.bt-inv-2&select=status,paid_at');\n    at /var/www/360lm/tests/sales_billing.spec.js:337:20"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:48.180Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-71b2a-lance-flips-invoice-to-paid-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 20,
                        "line": 337
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-db8ce2afdc7db14c029c",
              "file": "sales_billing.spec.js",
              "line": 325,
              "column": 3
            },
            {
              "title": "invoice_payment_summary view updates after receipt insert",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1704,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'balance_due')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'balance_due')\n    at /var/www/360lm/tests/sales_billing.spec.js:352:40",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 40,
                          "line": 352
                        },
                        "snippet": "  350 |   test('invoice_payment_summary view updates after receipt insert', async () => {\n  351 |     const before = await dbGet('/invoice_payment_summary?invoice_id=eq.bt-inv-2');\n> 352 |     const balBefore = Number(before[0].balance_due);\n      |                                        ^\n  353 |     expect(balBefore).toBeGreaterThan(0);\n  354 |     // Add a small part-payment\n  355 |     const probeId = 'bt-partial-' + RUN_ID;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 40,
                            "line": 352
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'balance_due')\n\n  350 |   test('invoice_payment_summary view updates after receipt insert', async () => {\n  351 |     const before = await dbGet('/invoice_payment_summary?invoice_id=eq.bt-inv-2');\n> 352 |     const balBefore = Number(before[0].balance_due);\n      |                                        ^\n  353 |     expect(balBefore).toBeGreaterThan(0);\n  354 |     // Add a small part-payment\n  355 |     const probeId = 'bt-partial-' + RUN_ID;\n    at /var/www/360lm/tests/sales_billing.spec.js:352:40"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:49.092Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-a58f6-pdates-after-receipt-insert-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 40,
                        "line": 352
                      }
                    },
                    {
                      "workerIndex": 1705,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'balance_due')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'balance_due')\n    at /var/www/360lm/tests/sales_billing.spec.js:352:40",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 40,
                          "line": 352
                        },
                        "snippet": "  350 |   test('invoice_payment_summary view updates after receipt insert', async () => {\n  351 |     const before = await dbGet('/invoice_payment_summary?invoice_id=eq.bt-inv-2');\n> 352 |     const balBefore = Number(before[0].balance_due);\n      |                                        ^\n  353 |     expect(balBefore).toBeGreaterThan(0);\n  354 |     // Add a small part-payment\n  355 |     const probeId = 'bt-partial-' + RUN_ID;"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 40,
                            "line": 352
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'balance_due')\n\n  350 |   test('invoice_payment_summary view updates after receipt insert', async () => {\n  351 |     const before = await dbGet('/invoice_payment_summary?invoice_id=eq.bt-inv-2');\n> 352 |     const balBefore = Number(before[0].balance_due);\n      |                                        ^\n  353 |     expect(balBefore).toBeGreaterThan(0);\n  354 |     // Add a small part-payment\n  355 |     const probeId = 'bt-partial-' + RUN_ID;\n    at /var/www/360lm/tests/sales_billing.spec.js:352:40"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:49.989Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-a58f6-pdates-after-receipt-insert-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 40,
                        "line": 352
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-faac141af811ac6cb375",
              "file": "sales_billing.spec.js",
              "line": 350,
              "column": 3
            },
            {
              "title": "payment modal present in HTML",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1706,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 77,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:369:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 369
                        },
                        "snippet": "  367 |     const ctx = await request.newContext();\n  368 |     const html = await (await ctx.get(BASE_URL)).text();\n> 369 |     expect(html).toContain('id=\"modal-payment\"');\n      |                  ^\n  370 |     expect(html).toContain('id=\"pay-amount\"');\n  371 |     expect(html).toContain('id=\"pay-mode\"');\n  372 |     expect(html).toContain('id=\"pay-ref\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 369
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  367 |     const ctx = await request.newContext();\n  368 |     const html = await (await ctx.get(BASE_URL)).text();\n> 369 |     expect(html).toContain('id=\"modal-payment\"');\n      |                  ^\n  370 |     expect(html).toContain('id=\"pay-amount\"');\n  371 |     expect(html).toContain('id=\"pay-mode\"');\n  372 |     expect(html).toContain('id=\"pay-ref\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:369:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:50.945Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-afa01-yment-modal-present-in-HTML-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 369
                      }
                    },
                    {
                      "workerIndex": 1707,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:369:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 369
                        },
                        "snippet": "  367 |     const ctx = await request.newContext();\n  368 |     const html = await (await ctx.get(BASE_URL)).text();\n> 369 |     expect(html).toContain('id=\"modal-payment\"');\n      |                  ^\n  370 |     expect(html).toContain('id=\"pay-amount\"');\n  371 |     expect(html).toContain('id=\"pay-mode\"');\n  372 |     expect(html).toContain('id=\"pay-ref\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 369
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-payment\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  367 |     const ctx = await request.newContext();\n  368 |     const html = await (await ctx.get(BASE_URL)).text();\n> 369 |     expect(html).toContain('id=\"modal-payment\"');\n      |                  ^\n  370 |     expect(html).toContain('id=\"pay-amount\"');\n  371 |     expect(html).toContain('id=\"pay-mode\"');\n  372 |     expect(html).toContain('id=\"pay-ref\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:369:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:51.984Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-afa01-yment-modal-present-in-HTML-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 369
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-894a2804f368c26801d7",
              "file": "sales_billing.spec.js",
              "line": 366,
              "column": 3
            },
            {
              "title": "Custodian invoice-link fields present in custodian HTML",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1708,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:380:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 380
                        },
                        "snippet": "  378 |     const ctx = await request.newContext();\n  379 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 380 |     expect(html).toContain('id=\"tf-invoice-link-group\"');\n      |                  ^\n  381 |     expect(html).toContain('id=\"tf-invoice-link\"');\n  382 |     expect(html).toContain('loadPartyInvoices');\n  383 |     expect(html).toMatch(/custodian-v\\d+/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 380
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  378 |     const ctx = await request.newContext();\n  379 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 380 |     expect(html).toContain('id=\"tf-invoice-link-group\"');\n      |                  ^\n  381 |     expect(html).toContain('id=\"tf-invoice-link\"');\n  382 |     expect(html).toContain('loadPartyInvoices');\n  383 |     expect(html).toMatch(/custodian-v\\d+/);\n    at /var/www/360lm/tests/sales_billing.spec.js:380:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:52.992Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-be768-s-present-in-custodian-HTML-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 380
                      }
                    },
                    {
                      "workerIndex": 1709,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:380:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 380
                        },
                        "snippet": "  378 |     const ctx = await request.newContext();\n  379 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 380 |     expect(html).toContain('id=\"tf-invoice-link-group\"');\n      |                  ^\n  381 |     expect(html).toContain('id=\"tf-invoice-link\"');\n  382 |     expect(html).toContain('loadPartyInvoices');\n  383 |     expect(html).toMatch(/custodian-v\\d+/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 380
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-invoice-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  378 |     const ctx = await request.newContext();\n  379 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 380 |     expect(html).toContain('id=\"tf-invoice-link-group\"');\n      |                  ^\n  381 |     expect(html).toContain('id=\"tf-invoice-link\"');\n  382 |     expect(html).toContain('loadPartyInvoices');\n  383 |     expect(html).toMatch(/custodian-v\\d+/);\n    at /var/www/360lm/tests/sales_billing.spec.js:380:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:54.004Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-be768-s-present-in-custodian-HTML-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 380
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-bbc2492678fd51052174",
              "file": "sales_billing.spec.js",
              "line": 377,
              "column": 3
            }
          ]
        },
        {
          "title": "Sales Billing — Cross-PWA: Stores ↔ Sales",
          "file": "sales_billing.spec.js",
          "line": 394,
          "column": 6,
          "specs": [
            {
              "title": "stores.inward has status + payment trigger working",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 702,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 71,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:52.332Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-09c749d6969f04b86517",
              "file": "sales_billing.spec.js",
              "line": 395,
              "column": 3
            },
            {
              "title": "payables_kpi view returns non-error",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 702,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 22,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:52.453Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-0cd5316f53944532e69f",
              "file": "sales_billing.spec.js",
              "line": 400,
              "column": 3
            },
            {
              "title": "job_material_cost rolls up issues per job",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 410,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 702,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:52.499Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 410,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-f7eca347bf4ebd1cffa6",
              "file": "sales_billing.spec.js",
              "line": 410,
              "column": 8
            },
            {
              "title": "job_profitability_v shows positive margin for paid invoice",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 425,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 702,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:52.500Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 425,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-b7af5bce65a8f1bfb222",
              "file": "sales_billing.spec.js",
              "line": 425,
              "column": 8
            },
            {
              "title": "job_profitability_v shows negative margin when only costs (no invoice)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 437,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 702,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:52.500Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 437,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-631339f44f773424aee9",
              "file": "sales_billing.spec.js",
              "line": 437,
              "column": 8
            },
            {
              "title": "item_avg_cost computes weighted average from inward_lines",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 702,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 23,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:449:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 449
                        },
                        "snippet": "  447 |     // Items have a single inward batch each in the test seed, so WAC = unit_price\n  448 |     const rows = await dbGet('/item_avg_cost?inward_batches=gte.1&limit=5', 'stores');\n> 449 |     expect(rows.length).toBeGreaterThan(0);\n      |                         ^\n  450 |     rows.forEach(r => {\n  451 |       expect(Number(r.avg_unit_cost)).toBeGreaterThan(0);\n  452 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 449
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m\n\n  447 |     // Items have a single inward batch each in the test seed, so WAC = unit_price\n  448 |     const rows = await dbGet('/item_avg_cost?inward_batches=gte.1&limit=5', 'stores');\n> 449 |     expect(rows.length).toBeGreaterThan(0);\n      |                         ^\n  450 |     rows.forEach(r => {\n  451 |       expect(Number(r.avg_unit_cost)).toBeGreaterThan(0);\n  452 |     });\n    at /var/www/360lm/tests/sales_billing.spec.js:449:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:52.500Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-31904-d-average-from-inward-lines-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 449
                      }
                    },
                    {
                      "workerIndex": 703,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 76,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:449:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 449
                        },
                        "snippet": "  447 |     // Items have a single inward batch each in the test seed, so WAC = unit_price\n  448 |     const rows = await dbGet('/item_avg_cost?inward_batches=gte.1&limit=5', 'stores');\n> 449 |     expect(rows.length).toBeGreaterThan(0);\n      |                         ^\n  450 |     rows.forEach(r => {\n  451 |       expect(Number(r.avg_unit_cost)).toBeGreaterThan(0);\n  452 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 449
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m\n\n  447 |     // Items have a single inward batch each in the test seed, so WAC = unit_price\n  448 |     const rows = await dbGet('/item_avg_cost?inward_batches=gte.1&limit=5', 'stores');\n> 449 |     expect(rows.length).toBeGreaterThan(0);\n      |                         ^\n  450 |     rows.forEach(r => {\n  451 |       expect(Number(r.avg_unit_cost)).toBeGreaterThan(0);\n  452 |     });\n    at /var/www/360lm/tests/sales_billing.spec.js:449:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:53.432Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-31904-d-average-from-inward-lines-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 449
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-fc16c23b16eda97dcebb",
              "file": "sales_billing.spec.js",
              "line": 446,
              "column": 3
            },
            {
              "title": "Sales PWA has job profitability + materials JS functions",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 704,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:458:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 458
                        },
                        "snippet": "  456 |     const ctx = await request.newContext();\n  457 |     const html = await (await ctx.get(BASE_URL)).text();\n> 458 |     expect(html).toContain('loadJobProfitability');\n      |                  ^\n  459 |     expect(html).toContain('loadJobMaterials');\n  460 |     expect(html).toContain('job-profit-card');\n  461 |     expect(html).toContain('job-materials-card');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 458
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  456 |     const ctx = await request.newContext();\n  457 |     const html = await (await ctx.get(BASE_URL)).text();\n> 458 |     expect(html).toContain('loadJobProfitability');\n      |                  ^\n  459 |     expect(html).toContain('loadJobMaterials');\n  460 |     expect(html).toContain('job-profit-card');\n  461 |     expect(html).toContain('job-materials-card');\n    at /var/www/360lm/tests/sales_billing.spec.js:458:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:54.385Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-64ced-lity-materials-JS-functions-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 458
                      }
                    },
                    {
                      "workerIndex": 705,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:458:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 458
                        },
                        "snippet": "  456 |     const ctx = await request.newContext();\n  457 |     const html = await (await ctx.get(BASE_URL)).text();\n> 458 |     expect(html).toContain('loadJobProfitability');\n      |                  ^\n  459 |     expect(html).toContain('loadJobMaterials');\n  460 |     expect(html).toContain('job-profit-card');\n  461 |     expect(html).toContain('job-materials-card');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 458
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  456 |     const ctx = await request.newContext();\n  457 |     const html = await (await ctx.get(BASE_URL)).text();\n> 458 |     expect(html).toContain('loadJobProfitability');\n      |                  ^\n  459 |     expect(html).toContain('loadJobMaterials');\n  460 |     expect(html).toContain('job-profit-card');\n  461 |     expect(html).toContain('job-materials-card');\n    at /var/www/360lm/tests/sales_billing.spec.js:458:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:55.293Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-64ced-lity-materials-JS-functions-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 458
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-5e8ef4663b483e63ebec",
              "file": "sales_billing.spec.js",
              "line": 455,
              "column": 3
            },
            {
              "title": "Custodian PWA has vendor bill link fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 706,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:468:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 468
                        },
                        "snippet": "  466 |     const ctx = await request.newContext();\n  467 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 468 |     expect(html).toContain('id=\"tf-vbill-link-group\"');\n      |                  ^\n  469 |     expect(html).toContain('id=\"tf-vbill-link\"');\n  470 |     expect(html).toContain('loadPartyBills');\n  471 |     expect(html).toMatch(/custodian-v\\d+/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 468
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  466 |     const ctx = await request.newContext();\n  467 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 468 |     expect(html).toContain('id=\"tf-vbill-link-group\"');\n      |                  ^\n  469 |     expect(html).toContain('id=\"tf-vbill-link\"');\n  470 |     expect(html).toContain('loadPartyBills');\n  471 |     expect(html).toMatch(/custodian-v\\d+/);\n    at /var/www/360lm/tests/sales_billing.spec.js:468:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:56.130Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-b8114-has-vendor-bill-link-fields-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 468
                      }
                    },
                    {
                      "workerIndex": 707,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:468:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 468
                        },
                        "snippet": "  466 |     const ctx = await request.newContext();\n  467 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 468 |     expect(html).toContain('id=\"tf-vbill-link-group\"');\n      |                  ^\n  469 |     expect(html).toContain('id=\"tf-vbill-link\"');\n  470 |     expect(html).toContain('loadPartyBills');\n  471 |     expect(html).toMatch(/custodian-v\\d+/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 468
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  466 |     const ctx = await request.newContext();\n  467 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 468 |     expect(html).toContain('id=\"tf-vbill-link-group\"');\n      |                  ^\n  469 |     expect(html).toContain('id=\"tf-vbill-link\"');\n  470 |     expect(html).toContain('loadPartyBills');\n  471 |     expect(html).toMatch(/custodian-v\\d+/);\n    at /var/www/360lm/tests/sales_billing.spec.js:468:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:57.059Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-b8114-has-vendor-bill-link-fields-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 468
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-6523978e661071c772ad",
              "file": "sales_billing.spec.js",
              "line": 465,
              "column": 3
            },
            {
              "title": "stores.inward has status + payment trigger working",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1710,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 71,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:55.005Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-059e1a3db7c6aaf5abc8",
              "file": "sales_billing.spec.js",
              "line": 395,
              "column": 3
            },
            {
              "title": "payables_kpi view returns non-error",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1710,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 20,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:55.119Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-62a7694acfada42987d9",
              "file": "sales_billing.spec.js",
              "line": 400,
              "column": 3
            },
            {
              "title": "job_material_cost rolls up issues per job",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 410,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1710,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:55.166Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 410,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-5a03013660ef627e22e8",
              "file": "sales_billing.spec.js",
              "line": 410,
              "column": 8
            },
            {
              "title": "job_profitability_v shows positive margin for paid invoice",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 425,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1710,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:55.167Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 425,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-1eca6e0d1e75ed8a0ffc",
              "file": "sales_billing.spec.js",
              "line": 425,
              "column": 8
            },
            {
              "title": "job_profitability_v shows negative margin when only costs (no invoice)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 437,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1710,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:55.167Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 437,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-71f02e524d322ccca998",
              "file": "sales_billing.spec.js",
              "line": 437,
              "column": 8
            },
            {
              "title": "item_avg_cost computes weighted average from inward_lines",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1710,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 20,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:449:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 449
                        },
                        "snippet": "  447 |     // Items have a single inward batch each in the test seed, so WAC = unit_price\n  448 |     const rows = await dbGet('/item_avg_cost?inward_batches=gte.1&limit=5', 'stores');\n> 449 |     expect(rows.length).toBeGreaterThan(0);\n      |                         ^\n  450 |     rows.forEach(r => {\n  451 |       expect(Number(r.avg_unit_cost)).toBeGreaterThan(0);\n  452 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 449
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m\n\n  447 |     // Items have a single inward batch each in the test seed, so WAC = unit_price\n  448 |     const rows = await dbGet('/item_avg_cost?inward_batches=gte.1&limit=5', 'stores');\n> 449 |     expect(rows.length).toBeGreaterThan(0);\n      |                         ^\n  450 |     rows.forEach(r => {\n  451 |       expect(Number(r.avg_unit_cost)).toBeGreaterThan(0);\n  452 |     });\n    at /var/www/360lm/tests/sales_billing.spec.js:449:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:55.167Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-31904-d-average-from-inward-lines-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 449
                      }
                    },
                    {
                      "workerIndex": 1711,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 75,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:449:25",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 25,
                          "line": 449
                        },
                        "snippet": "  447 |     // Items have a single inward batch each in the test seed, so WAC = unit_price\n  448 |     const rows = await dbGet('/item_avg_cost?inward_batches=gte.1&limit=5', 'stores');\n> 449 |     expect(rows.length).toBeGreaterThan(0);\n      |                         ^\n  450 |     rows.forEach(r => {\n  451 |       expect(Number(r.avg_unit_cost)).toBeGreaterThan(0);\n  452 |     });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 25,
                            "line": 449
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeGreaterThan\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected: > \u001b[32m0\u001b[39m\nReceived:   \u001b[31m0\u001b[39m\n\n  447 |     // Items have a single inward batch each in the test seed, so WAC = unit_price\n  448 |     const rows = await dbGet('/item_avg_cost?inward_batches=gte.1&limit=5', 'stores');\n> 449 |     expect(rows.length).toBeGreaterThan(0);\n      |                         ^\n  450 |     rows.forEach(r => {\n  451 |       expect(Number(r.avg_unit_cost)).toBeGreaterThan(0);\n  452 |     });\n    at /var/www/360lm/tests/sales_billing.spec.js:449:25"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:56.173Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-31904-d-average-from-inward-lines-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 25,
                        "line": 449
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-63af6e05204e85386392",
              "file": "sales_billing.spec.js",
              "line": 446,
              "column": 3
            },
            {
              "title": "Sales PWA has job profitability + materials JS functions",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1712,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:458:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 458
                        },
                        "snippet": "  456 |     const ctx = await request.newContext();\n  457 |     const html = await (await ctx.get(BASE_URL)).text();\n> 458 |     expect(html).toContain('loadJobProfitability');\n      |                  ^\n  459 |     expect(html).toContain('loadJobMaterials');\n  460 |     expect(html).toContain('job-profit-card');\n  461 |     expect(html).toContain('job-materials-card');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 458
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  456 |     const ctx = await request.newContext();\n  457 |     const html = await (await ctx.get(BASE_URL)).text();\n> 458 |     expect(html).toContain('loadJobProfitability');\n      |                  ^\n  459 |     expect(html).toContain('loadJobMaterials');\n  460 |     expect(html).toContain('job-profit-card');\n  461 |     expect(html).toContain('job-materials-card');\n    at /var/www/360lm/tests/sales_billing.spec.js:458:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:57.151Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-64ced-lity-materials-JS-functions-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 458
                      }
                    },
                    {
                      "workerIndex": 1713,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:458:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 458
                        },
                        "snippet": "  456 |     const ctx = await request.newContext();\n  457 |     const html = await (await ctx.get(BASE_URL)).text();\n> 458 |     expect(html).toContain('loadJobProfitability');\n      |                  ^\n  459 |     expect(html).toContain('loadJobMaterials');\n  460 |     expect(html).toContain('job-profit-card');\n  461 |     expect(html).toContain('job-materials-card');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 458
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"loadJobProfitability\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  456 |     const ctx = await request.newContext();\n  457 |     const html = await (await ctx.get(BASE_URL)).text();\n> 458 |     expect(html).toContain('loadJobProfitability');\n      |                  ^\n  459 |     expect(html).toContain('loadJobMaterials');\n  460 |     expect(html).toContain('job-profit-card');\n  461 |     expect(html).toContain('job-materials-card');\n    at /var/www/360lm/tests/sales_billing.spec.js:458:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:47:58.177Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-64ced-lity-materials-JS-functions-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 458
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-23a811a24707529685f4",
              "file": "sales_billing.spec.js",
              "line": 455,
              "column": 3
            },
            {
              "title": "Custodian PWA has vendor bill link fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1714,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:468:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 468
                        },
                        "snippet": "  466 |     const ctx = await request.newContext();\n  467 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 468 |     expect(html).toContain('id=\"tf-vbill-link-group\"');\n      |                  ^\n  469 |     expect(html).toContain('id=\"tf-vbill-link\"');\n  470 |     expect(html).toContain('loadPartyBills');\n  471 |     expect(html).toMatch(/custodian-v\\d+/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 468
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  466 |     const ctx = await request.newContext();\n  467 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 468 |     expect(html).toContain('id=\"tf-vbill-link-group\"');\n      |                  ^\n  469 |     expect(html).toContain('id=\"tf-vbill-link\"');\n  470 |     expect(html).toContain('loadPartyBills');\n  471 |     expect(html).toMatch(/custodian-v\\d+/);\n    at /var/www/360lm/tests/sales_billing.spec.js:468:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:47:59.180Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-b8114-has-vendor-bill-link-fields-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 468
                      }
                    },
                    {
                      "workerIndex": 1715,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:468:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 468
                        },
                        "snippet": "  466 |     const ctx = await request.newContext();\n  467 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 468 |     expect(html).toContain('id=\"tf-vbill-link-group\"');\n      |                  ^\n  469 |     expect(html).toContain('id=\"tf-vbill-link\"');\n  470 |     expect(html).toContain('loadPartyBills');\n  471 |     expect(html).toMatch(/custodian-v\\d+/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 468
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"tf-vbill-link-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  466 |     const ctx = await request.newContext();\n  467 |     const html = await (await ctx.get('https://srv1111289.hstgr.cloud/finance/custodian/')).text();\n> 468 |     expect(html).toContain('id=\"tf-vbill-link-group\"');\n      |                  ^\n  469 |     expect(html).toContain('id=\"tf-vbill-link\"');\n  470 |     expect(html).toContain('loadPartyBills');\n  471 |     expect(html).toMatch(/custodian-v\\d+/);\n    at /var/www/360lm/tests/sales_billing.spec.js:468:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:48:00.245Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-b8114-has-vendor-bill-link-fields-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 468
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-5ec9d4e05b602e6e76be",
              "file": "sales_billing.spec.js",
              "line": 465,
              "column": 3
            }
          ]
        },
        {
          "title": "Sales Billing — Cross-stage Integrity",
          "file": "sales_billing.spec.js",
          "line": 476,
          "column": 6,
          "specs": [
            {
              "title": "every challan with an invoice_id links to a real invoice",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 708,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 64,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:57.953Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-84e003c086879e2afec8",
              "file": "sales_billing.spec.js",
              "line": 477,
              "column": 3
            },
            {
              "title": "every invoice with job_id links to a real job",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 708,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 19,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:58.066Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-accde93380aa6c8c373d",
              "file": "sales_billing.spec.js",
              "line": 486,
              "column": 3
            },
            {
              "title": "invoice_receipts sum ≥ grand_total → invoice is paid",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 708,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 14,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n    at /var/www/360lm/tests/sales_billing.spec.js:498:21",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 21,
                          "line": 498
                        },
                        "snippet": "  496 |     const s = summary[0];\n  497 |     // bt-inv-3 total = 1002056, receipts = 500000 + 502056 = 1002056\n> 498 |     expect(Number(s.paid_amount)).toBeGreaterThanOrEqual(Number(s.grand_total) - 1);\n      |                     ^\n  499 |     expect(s.status).toBe('paid');\n  500 |   });\n  501 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 21,
                            "line": 498
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n\n  496 |     const s = summary[0];\n  497 |     // bt-inv-3 total = 1002056, receipts = 500000 + 502056 = 1002056\n> 498 |     expect(Number(s.paid_amount)).toBeGreaterThanOrEqual(Number(s.grand_total) - 1);\n      |                     ^\n  499 |     expect(s.status).toBe('paid');\n  500 |   });\n  501 |\n    at /var/www/360lm/tests/sales_billing.spec.js:498:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:58.115Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-42e0a-and-total-→-invoice-is-paid-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 21,
                        "line": 498
                      }
                    },
                    {
                      "workerIndex": 709,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 72,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n    at /var/www/360lm/tests/sales_billing.spec.js:498:21",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 21,
                          "line": 498
                        },
                        "snippet": "  496 |     const s = summary[0];\n  497 |     // bt-inv-3 total = 1002056, receipts = 500000 + 502056 = 1002056\n> 498 |     expect(Number(s.paid_amount)).toBeGreaterThanOrEqual(Number(s.grand_total) - 1);\n      |                     ^\n  499 |     expect(s.status).toBe('paid');\n  500 |   });\n  501 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 21,
                            "line": 498
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n\n  496 |     const s = summary[0];\n  497 |     // bt-inv-3 total = 1002056, receipts = 500000 + 502056 = 1002056\n> 498 |     expect(Number(s.paid_amount)).toBeGreaterThanOrEqual(Number(s.grand_total) - 1);\n      |                     ^\n  499 |     expect(s.status).toBe('paid');\n  500 |   });\n  501 |\n    at /var/www/360lm/tests/sales_billing.spec.js:498:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:47:58.960Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-42e0a-and-total-→-invoice-is-paid-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 21,
                        "line": 498
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-4b79ed3b74ab64b10da3",
              "file": "sales_billing.spec.js",
              "line": 494,
              "column": 3
            },
            {
              "title": "360DLM company in invoice resolved by getCompany cache (not hardcoded)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 710,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:508:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 508
                        },
                        "snippet": "  506 |     expect(html).not.toContain('const COMPANIES =');\n  507 |     // Must have getCompany function\n> 508 |     expect(html).toContain('function getCompany(');\n      |                  ^\n  509 |     expect(html).toContain('_companiesCache');\n  510 |     await ctx.dispose();\n  511 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 508
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  506 |     expect(html).not.toContain('const COMPANIES =');\n  507 |     // Must have getCompany function\n> 508 |     expect(html).toContain('function getCompany(');\n      |                  ^\n  509 |     expect(html).toContain('_companiesCache');\n  510 |     await ctx.dispose();\n  511 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:508:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:47:59.906Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-af910-ompany-cache-not-hardcoded--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 508
                      }
                    },
                    {
                      "workerIndex": 711,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 67,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:508:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 508
                        },
                        "snippet": "  506 |     expect(html).not.toContain('const COMPANIES =');\n  507 |     // Must have getCompany function\n> 508 |     expect(html).toContain('function getCompany(');\n      |                  ^\n  509 |     expect(html).toContain('_companiesCache');\n  510 |     await ctx.dispose();\n  511 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 508
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  506 |     expect(html).not.toContain('const COMPANIES =');\n  507 |     // Must have getCompany function\n> 508 |     expect(html).toContain('function getCompany(');\n      |                  ^\n  509 |     expect(html).toContain('_companiesCache');\n  510 |     await ctx.dispose();\n  511 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:508:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:48:01.006Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-af910-ompany-cache-not-hardcoded--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 508
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-daa2c54712fa56c238d5",
              "file": "sales_billing.spec.js",
              "line": 502,
              "column": 3
            },
            {
              "title": "gst_type matches company vs client state codes",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 712,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 142,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:48:03.318Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-cafd74d55d0ab56318f0",
              "file": "sales_billing.spec.js",
              "line": 513,
              "column": 3
            },
            {
              "title": "every challan with an invoice_id links to a real invoice",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1716,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 92,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:48:01.541Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-475562a4a7496434391b",
              "file": "sales_billing.spec.js",
              "line": 477,
              "column": 3
            },
            {
              "title": "every invoice with job_id links to a real job",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1716,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 52,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:48:01.705Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-8381dea505235c0465de",
              "file": "sales_billing.spec.js",
              "line": 486,
              "column": 3
            },
            {
              "title": "invoice_receipts sum ≥ grand_total → invoice is paid",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1716,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n    at /var/www/360lm/tests/sales_billing.spec.js:498:21",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 21,
                          "line": 498
                        },
                        "snippet": "  496 |     const s = summary[0];\n  497 |     // bt-inv-3 total = 1002056, receipts = 500000 + 502056 = 1002056\n> 498 |     expect(Number(s.paid_amount)).toBeGreaterThanOrEqual(Number(s.grand_total) - 1);\n      |                     ^\n  499 |     expect(s.status).toBe('paid');\n  500 |   });\n  501 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 21,
                            "line": 498
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n\n  496 |     const s = summary[0];\n  497 |     // bt-inv-3 total = 1002056, receipts = 500000 + 502056 = 1002056\n> 498 |     expect(Number(s.paid_amount)).toBeGreaterThanOrEqual(Number(s.grand_total) - 1);\n      |                     ^\n  499 |     expect(s.status).toBe('paid');\n  500 |   });\n  501 |\n    at /var/www/360lm/tests/sales_billing.spec.js:498:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:48:01.828Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-42e0a-and-total-→-invoice-is-paid-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 21,
                        "line": 498
                      }
                    },
                    {
                      "workerIndex": 1717,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 92,
                      "error": {
                        "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')",
                        "stack": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n    at /var/www/360lm/tests/sales_billing.spec.js:498:21",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 21,
                          "line": 498
                        },
                        "snippet": "  496 |     const s = summary[0];\n  497 |     // bt-inv-3 total = 1002056, receipts = 500000 + 502056 = 1002056\n> 498 |     expect(Number(s.paid_amount)).toBeGreaterThanOrEqual(Number(s.grand_total) - 1);\n      |                     ^\n  499 |     expect(s.status).toBe('paid');\n  500 |   });\n  501 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 21,
                            "line": 498
                          },
                          "message": "TypeError: Cannot read properties of undefined (reading 'paid_amount')\n\n  496 |     const s = summary[0];\n  497 |     // bt-inv-3 total = 1002056, receipts = 500000 + 502056 = 1002056\n> 498 |     expect(Number(s.paid_amount)).toBeGreaterThanOrEqual(Number(s.grand_total) - 1);\n      |                     ^\n  499 |     expect(s.status).toBe('paid');\n  500 |   });\n  501 |\n    at /var/www/360lm/tests/sales_billing.spec.js:498:21"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:48:03.177Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-42e0a-and-total-→-invoice-is-paid-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 21,
                        "line": 498
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-e8e542f7f677379fa291",
              "file": "sales_billing.spec.js",
              "line": 494,
              "column": 3
            },
            {
              "title": "360DLM company in invoice resolved by getCompany cache (not hardcoded)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1718,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 73,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:508:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 508
                        },
                        "snippet": "  506 |     expect(html).not.toContain('const COMPANIES =');\n  507 |     // Must have getCompany function\n> 508 |     expect(html).toContain('function getCompany(');\n      |                  ^\n  509 |     expect(html).toContain('_companiesCache');\n  510 |     await ctx.dispose();\n  511 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 508
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  506 |     expect(html).not.toContain('const COMPANIES =');\n  507 |     // Must have getCompany function\n> 508 |     expect(html).toContain('function getCompany(');\n      |                  ^\n  509 |     expect(html).toContain('_companiesCache');\n  510 |     await ctx.dispose();\n  511 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:508:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:48:04.529Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-af910-ompany-cache-not-hardcoded--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 508
                      }
                    },
                    {
                      "workerIndex": 1719,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 90,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:508:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 508
                        },
                        "snippet": "  506 |     expect(html).not.toContain('const COMPANIES =');\n  507 |     // Must have getCompany function\n> 508 |     expect(html).toContain('function getCompany(');\n      |                  ^\n  509 |     expect(html).toContain('_companiesCache');\n  510 |     await ctx.dispose();\n  511 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 508
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function getCompany(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  506 |     expect(html).not.toContain('const COMPANIES =');\n  507 |     // Must have getCompany function\n> 508 |     expect(html).toContain('function getCompany(');\n      |                  ^\n  509 |     expect(html).toContain('_companiesCache');\n  510 |     await ctx.dispose();\n  511 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:508:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:48:05.787Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-af910-ompany-cache-not-hardcoded--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 508
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-95d95f5e8162dffa856c",
              "file": "sales_billing.spec.js",
              "line": 502,
              "column": 3
            },
            {
              "title": "gst_type matches company vs client state codes",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1720,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 69,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:48:06.844Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-822afe41cf880c3ab577",
              "file": "sales_billing.spec.js",
              "line": 513,
              "column": 3
            }
          ]
        },
        {
          "title": "Sales Billing — Rendered UI",
          "file": "sales_billing.spec.js",
          "line": 526,
          "column": 6,
          "specs": [
            {
              "title": "Dashboard loads and shows Billed ₹ KPI",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 712,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6202,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n\n    at /var/www/360lm/tests/sales_billing.spec.js:531:47",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 47,
                          "line": 531
                        },
                        "snippet": "  529 |     await page.goto(BASE_URL);\n  530 |     await page.waitForTimeout(800);\n> 531 |     await expect(page.locator('#kpi-billed')).toBeVisible();\n      |                                               ^\n  532 |     const billed = await page.locator('#kpi-billed').textContent();\n  533 |     expect(billed).not.toBe('—');\n  534 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 47,
                            "line": 531
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n\n\n  529 |     await page.goto(BASE_URL);\n  530 |     await page.waitForTimeout(800);\n> 531 |     await expect(page.locator('#kpi-billed')).toBeVisible();\n      |                                               ^\n  532 |     const billed = await page.locator('#kpi-billed').textContent();\n  533 |     expect(billed).not.toBe('—');\n  534 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:531:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:48:03.587Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e30-oads-and-shows-Billed-₹-KPI-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e30-oads-and-shows-Billed-₹-KPI-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 47,
                        "line": 531
                      }
                    },
                    {
                      "workerIndex": 713,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6057,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n\n    at /var/www/360lm/tests/sales_billing.spec.js:531:47",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 47,
                          "line": 531
                        },
                        "snippet": "  529 |     await page.goto(BASE_URL);\n  530 |     await page.waitForTimeout(800);\n> 531 |     await expect(page.locator('#kpi-billed')).toBeVisible();\n      |                                               ^\n  532 |     const billed = await page.locator('#kpi-billed').textContent();\n  533 |     expect(billed).not.toBe('—');\n  534 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 47,
                            "line": 531
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n\n\n  529 |     await page.goto(BASE_URL);\n  530 |     await page.waitForTimeout(800);\n> 531 |     await expect(page.locator('#kpi-billed')).toBeVisible();\n      |                                               ^\n  532 |     const billed = await page.locator('#kpi-billed').textContent();\n  533 |     expect(billed).not.toBe('—');\n  534 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:531:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:48:10.938Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e30-oads-and-shows-Billed-₹-KPI-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e30-oads-and-shows-Billed-₹-KPI-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 47,
                        "line": 531
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-67a02daaf103187498ec",
              "file": "sales_billing.spec.js",
              "line": 527,
              "column": 3
            },
            {
              "title": "All Jobs screen shows bt-job-c with in_execution status",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 539,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 714,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:48:18.013Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 539,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-93d1c1fc1d0f8ad11893",
              "file": "sales_billing.spec.js",
              "line": 539,
              "column": 8
            },
            {
              "title": "Offers screen shows draft + sent + accepted chips",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 714,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30095,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 557
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-offers')\u001b[22m\n\n\n  555 |     await page.goto(BASE_URL);\n  556 |     await page.waitForTimeout(500);\n> 557 |     await page.click('#nav-offers');\n      |                ^\n  558 |     await page.waitForTimeout(700);\n  559 |     const statTags = await page.locator('#offers-list .tag').allTextContents();\n  560 |     const normalised = statTags.map(s => s.toLowerCase());\n    at /var/www/360lm/tests/sales_billing.spec.js:557:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:48:18.014Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-51d9f-s-draft-sent-accepted-chips-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-51d9f-s-draft-sent-accepted-chips-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 715,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30087,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 557
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-offers')\u001b[22m\n\n\n  555 |     await page.goto(BASE_URL);\n  556 |     await page.waitForTimeout(500);\n> 557 |     await page.click('#nav-offers');\n      |                ^\n  558 |     await page.waitForTimeout(700);\n  559 |     const statTags = await page.locator('#offers-list .tag').allTextContents();\n  560 |     const normalised = statTags.map(s => s.toLowerCase());\n    at /var/www/360lm/tests/sales_billing.spec.js:557:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:48:49.277Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-51d9f-s-draft-sent-accepted-chips-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-51d9f-s-draft-sent-accepted-chips-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-2ae56656f1fade0978e5",
              "file": "sales_billing.spec.js",
              "line": 553,
              "column": 3
            },
            {
              "title": "Offer form has HSN/SAC input and SVC/MAT toggle",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 716,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30075,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 568
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-offers')\u001b[22m\n\n\n  566 |     await page.goto(BASE_URL);\n  567 |     await page.waitForTimeout(400);\n> 568 |     await page.click('#nav-offers');\n      |                ^\n  569 |     await page.waitForTimeout(300);\n  570 |     await page.click('#btn-offers-new');\n  571 |     await page.waitForTimeout(400);\n    at /var/www/360lm/tests/sales_billing.spec.js:568:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:49:20.389Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f54aa-AC-input-and-SVC-MAT-toggle-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f54aa-AC-input-and-SVC-MAT-toggle-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 717,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 568
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-offers')\u001b[22m\n\n\n  566 |     await page.goto(BASE_URL);\n  567 |     await page.waitForTimeout(400);\n> 568 |     await page.click('#nav-offers');\n      |                ^\n  569 |     await page.waitForTimeout(300);\n  570 |     await page.click('#btn-offers-new');\n  571 |     await page.waitForTimeout(400);\n    at /var/www/360lm/tests/sales_billing.spec.js:568:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:49:51.422Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f54aa-AC-input-and-SVC-MAT-toggle-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f54aa-AC-input-and-SVC-MAT-toggle-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-250f33ad88892b7ab34c",
              "file": "sales_billing.spec.js",
              "line": 564,
              "column": 3
            },
            {
              "title": "Invoice form has company select + GST settings",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 718,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:589:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 589
                        },
                        "snippet": "  587 |     const html = await (await ctx.get(BASE_URL)).text();\n  588 |     await ctx.dispose();\n> 589 |     expect(html).toContain('id=\"inv-company-select\"');\n      |                  ^\n  590 |     expect(html).toContain('id=\"inv-gst-type\"');\n  591 |     expect(html).toContain('id=\"inv-po-no\"');\n  592 |     expect(html).toContain('id=\"inv-po-date\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 589
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  587 |     const html = await (await ctx.get(BASE_URL)).text();\n  588 |     await ctx.dispose();\n> 589 |     expect(html).toContain('id=\"inv-company-select\"');\n      |                  ^\n  590 |     expect(html).toContain('id=\"inv-gst-type\"');\n  591 |     expect(html).toContain('id=\"inv-po-no\"');\n  592 |     expect(html).toContain('id=\"inv-po-date\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:589:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:22.510Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-c35fd-company-select-GST-settings-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 589
                      }
                    },
                    {
                      "workerIndex": 719,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:589:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 589
                        },
                        "snippet": "  587 |     const html = await (await ctx.get(BASE_URL)).text();\n  588 |     await ctx.dispose();\n> 589 |     expect(html).toContain('id=\"inv-company-select\"');\n      |                  ^\n  590 |     expect(html).toContain('id=\"inv-gst-type\"');\n  591 |     expect(html).toContain('id=\"inv-po-no\"');\n  592 |     expect(html).toContain('id=\"inv-po-date\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 589
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  587 |     const html = await (await ctx.get(BASE_URL)).text();\n  588 |     await ctx.dispose();\n> 589 |     expect(html).toContain('id=\"inv-company-select\"');\n      |                  ^\n  590 |     expect(html).toContain('id=\"inv-gst-type\"');\n  591 |     expect(html).toContain('id=\"inv-po-no\"');\n  592 |     expect(html).toContain('id=\"inv-po-date\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:589:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:23.367Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-c35fd-company-select-GST-settings-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 589
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-3da5cd8dda31387d03e6",
              "file": "sales_billing.spec.js",
              "line": 584,
              "column": 3
            },
            {
              "title": "Challans tab visible on Job Detail",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 603,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:23.471Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 603,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-4ddbdbbe5e321f15cf29",
              "file": "sales_billing.spec.js",
              "line": 603,
              "column": 8
            },
            {
              "title": "Dashboard loads and shows Billed ₹ KPI",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1720,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6097,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n\n    at /var/www/360lm/tests/sales_billing.spec.js:531:47",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 47,
                          "line": 531
                        },
                        "snippet": "  529 |     await page.goto(BASE_URL);\n  530 |     await page.waitForTimeout(800);\n> 531 |     await expect(page.locator('#kpi-billed')).toBeVisible();\n      |                                               ^\n  532 |     const billed = await page.locator('#kpi-billed').textContent();\n  533 |     expect(billed).not.toBe('—');\n  534 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 47,
                            "line": 531
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n\n\n  529 |     await page.goto(BASE_URL);\n  530 |     await page.waitForTimeout(800);\n> 531 |     await expect(page.locator('#kpi-billed')).toBeVisible();\n      |                                               ^\n  532 |     const billed = await page.locator('#kpi-billed').textContent();\n  533 |     expect(billed).not.toBe('—');\n  534 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:531:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:48:06.976Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e30-oads-and-shows-Billed-₹-KPI-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e30-oads-and-shows-Billed-₹-KPI-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 47,
                        "line": 531
                      }
                    },
                    {
                      "workerIndex": 1721,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6069,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n\n    at /var/www/360lm/tests/sales_billing.spec.js:531:47",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 47,
                          "line": 531
                        },
                        "snippet": "  529 |     await page.goto(BASE_URL);\n  530 |     await page.waitForTimeout(800);\n> 531 |     await expect(page.locator('#kpi-billed')).toBeVisible();\n      |                                               ^\n  532 |     const billed = await page.locator('#kpi-billed').textContent();\n  533 |     expect(billed).not.toBe('—');\n  534 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 47,
                            "line": 531
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#kpi-billed')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#kpi-billed')\u001b[22m\n\n\n  529 |     await page.goto(BASE_URL);\n  530 |     await page.waitForTimeout(800);\n> 531 |     await expect(page.locator('#kpi-billed')).toBeVisible();\n      |                                               ^\n  532 |     const billed = await page.locator('#kpi-billed').textContent();\n  533 |     expect(billed).not.toBe('—');\n  534 |   });\n    at /var/www/360lm/tests/sales_billing.spec.js:531:47"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:48:14.104Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e30-oads-and-shows-Billed-₹-KPI-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-e6e30-oads-and-shows-Billed-₹-KPI-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 47,
                        "line": 531
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-6b259659b7a1b158b222",
              "file": "sales_billing.spec.js",
              "line": 527,
              "column": 3
            },
            {
              "title": "All Jobs screen shows bt-job-c with in_execution status",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 539,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1722,
                      "parallelIndex": 0,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:48:21.238Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 539,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-4adb5b2dea21dc309097",
              "file": "sales_billing.spec.js",
              "line": 539,
              "column": 8
            },
            {
              "title": "Offers screen shows draft + sent + accepted chips",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1722,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30087,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 557
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-offers')\u001b[22m\n\n\n  555 |     await page.goto(BASE_URL);\n  556 |     await page.waitForTimeout(500);\n> 557 |     await page.click('#nav-offers');\n      |                ^\n  558 |     await page.waitForTimeout(700);\n  559 |     const statTags = await page.locator('#offers-list .tag').allTextContents();\n  560 |     const normalised = statTags.map(s => s.toLowerCase());\n    at /var/www/360lm/tests/sales_billing.spec.js:557:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:48:21.240Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-51d9f-s-draft-sent-accepted-chips-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-51d9f-s-draft-sent-accepted-chips-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1723,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30104,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 557
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-offers')\u001b[22m\n\n\n  555 |     await page.goto(BASE_URL);\n  556 |     await page.waitForTimeout(500);\n> 557 |     await page.click('#nav-offers');\n      |                ^\n  558 |     await page.waitForTimeout(700);\n  559 |     const statTags = await page.locator('#offers-list .tag').allTextContents();\n  560 |     const normalised = statTags.map(s => s.toLowerCase());\n    at /var/www/360lm/tests/sales_billing.spec.js:557:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:48:52.413Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-51d9f-s-draft-sent-accepted-chips-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-51d9f-s-draft-sent-accepted-chips-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-c8dbadd929536123a601",
              "file": "sales_billing.spec.js",
              "line": 553,
              "column": 3
            },
            {
              "title": "Offer form has HSN/SAC input and SVC/MAT toggle",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1724,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 568
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-offers')\u001b[22m\n\n\n  566 |     await page.goto(BASE_URL);\n  567 |     await page.waitForTimeout(400);\n> 568 |     await page.click('#nav-offers');\n      |                ^\n  569 |     await page.waitForTimeout(300);\n  570 |     await page.click('#btn-offers-new');\n  571 |     await page.waitForTimeout(400);\n    at /var/www/360lm/tests/sales_billing.spec.js:568:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:49:23.534Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f54aa-AC-input-and-SVC-MAT-toggle-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f54aa-AC-input-and-SVC-MAT-toggle-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1725,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30093,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 16,
                            "line": 568
                          },
                          "message": "Error: page.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-offers')\u001b[22m\n\n\n  566 |     await page.goto(BASE_URL);\n  567 |     await page.waitForTimeout(400);\n> 568 |     await page.click('#nav-offers');\n      |                ^\n  569 |     await page.waitForTimeout(300);\n  570 |     await page.click('#btn-offers-new');\n  571 |     await page.waitForTimeout(400);\n    at /var/www/360lm/tests/sales_billing.spec.js:568:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:49:54.696Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f54aa-AC-input-and-SVC-MAT-toggle-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-f54aa-AC-input-and-SVC-MAT-toggle-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-e65f52f4074bd6e7b217",
              "file": "sales_billing.spec.js",
              "line": 564,
              "column": 3
            },
            {
              "title": "Invoice form has company select + GST settings",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1726,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 68,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:589:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 589
                        },
                        "snippet": "  587 |     const html = await (await ctx.get(BASE_URL)).text();\n  588 |     await ctx.dispose();\n> 589 |     expect(html).toContain('id=\"inv-company-select\"');\n      |                  ^\n  590 |     expect(html).toContain('id=\"inv-gst-type\"');\n  591 |     expect(html).toContain('id=\"inv-po-no\"');\n  592 |     expect(html).toContain('id=\"inv-po-date\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 589
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  587 |     const html = await (await ctx.get(BASE_URL)).text();\n  588 |     await ctx.dispose();\n> 589 |     expect(html).toContain('id=\"inv-company-select\"');\n      |                  ^\n  590 |     expect(html).toContain('id=\"inv-gst-type\"');\n  591 |     expect(html).toContain('id=\"inv-po-no\"');\n  592 |     expect(html).toContain('id=\"inv-po-date\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:589:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:25.891Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-c35fd-company-select-GST-settings-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 589
                      }
                    },
                    {
                      "workerIndex": 1727,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales_billing.spec.js:589:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales_billing.spec.js",
                          "column": 18,
                          "line": 589
                        },
                        "snippet": "  587 |     const html = await (await ctx.get(BASE_URL)).text();\n  588 |     await ctx.dispose();\n> 589 |     expect(html).toContain('id=\"inv-company-select\"');\n      |                  ^\n  590 |     expect(html).toContain('id=\"inv-gst-type\"');\n  591 |     expect(html).toContain('id=\"inv-po-no\"');\n  592 |     expect(html).toContain('id=\"inv-po-date\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "column": 18,
                            "line": 589
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"inv-company-select\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  587 |     const html = await (await ctx.get(BASE_URL)).text();\n  588 |     await ctx.dispose();\n> 589 |     expect(html).toContain('id=\"inv-company-select\"');\n      |                  ^\n  590 |     expect(html).toContain('id=\"inv-gst-type\"');\n  591 |     expect(html).toContain('id=\"inv-po-no\"');\n  592 |     expect(html).toContain('id=\"inv-po-date\"');\n    at /var/www/360lm/tests/sales_billing.spec.js:589:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:26.781Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales_billing-Sales-Billin-c35fd-company-select-GST-settings-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "column": 18,
                        "line": 589
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-dc3ad3e7c75f14490775",
              "file": "sales_billing.spec.js",
              "line": 584,
              "column": 3
            },
            {
              "title": "Challans tab visible on Job Detail",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [
                    {
                      "type": "skip",
                      "location": {
                        "file": "/var/www/360lm/tests/sales_billing.spec.js",
                        "line": 603,
                        "column": 8
                      }
                    }
                  ],
                  "expectedStatus": "skipped",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": -1,
                      "parallelIndex": -1,
                      "status": "skipped",
                      "duration": 0,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:26.889Z",
                      "annotations": [
                        {
                          "type": "skip",
                          "location": {
                            "file": "/var/www/360lm/tests/sales_billing.spec.js",
                            "line": 603,
                            "column": 8
                          }
                        }
                      ],
                      "attachments": []
                    }
                  ],
                  "status": "skipped"
                }
              ],
              "id": "e3b66ec69c8f5a0aee8e-0fc094731aafaa06f141",
              "file": "sales_billing.spec.js",
              "line": 603,
              "column": 8
            }
          ]
        }
      ]
    },
    {
      "title": "sales.spec.js",
      "file": "sales.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Sales — Smoke",
          "file": "sales.spec.js",
          "line": 10,
          "column": 6,
          "specs": [
            {
              "title": "page loads with login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 720,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 266,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:24.229Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-fd930b1c8cc9b3b11669",
              "file": "sales.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 720,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 197,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js contains current version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js contains current version', async ({ page }) => {\n    at /var/www/360lm/tests/sales.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:24.670Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 721,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 283,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js contains current version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js contains current version', async ({ page }) => {\n    at /var/www/360lm/tests/sales.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:25.701Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-a00c16728b49d60f5736",
              "file": "sales.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js contains current version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 722,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 297,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360sales-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360sales-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/sales.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:26.936Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-sw-js-contains-current-version-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-sw-js-contains-current-version-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 723,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 313,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360sales-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360sales-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/sales.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:28.322Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-sw-js-contains-current-version-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-sw-js-contains-current-version-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-e44bda16cf3af8c35e79",
              "file": "sales.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "page loads with login screen",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1728,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 327,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:27.679Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-d466939338448fd3ee1c",
              "file": "sales.spec.js",
              "line": 11,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1728,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 212,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js contains current version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js contains current version', async ({ page }) => {\n    at /var/www/360lm/tests/sales.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:28.206Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 1729,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 327,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js contains current version', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js contains current version', async ({ page }) => {\n    at /var/www/360lm/tests/sales.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:29.412Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-41e38a2ec94697eaa763",
              "file": "sales.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js contains current version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1730,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 320,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360sales-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360sales-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/sales.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:30.691Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-sw-js-contains-current-version-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-sw-js-contains-current-version-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 1731,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 336,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360sales-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360sales-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360sales-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/sales.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:32.075Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-sw-js-contains-current-version-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Smoke-sw-js-contains-current-version-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-df3cec116b91aaacea7b",
              "file": "sales.spec.js",
              "line": 22,
              "column": 3
            }
          ]
        },
        {
          "title": "Sales — Canonical Job Form",
          "file": "sales.spec.js",
          "line": 30,
          "column": 6,
          "specs": [
            {
              "title": "Phase field exists in job form",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 724,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 65,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:35:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 35
                        },
                        "snippet": "  33 |     const resp = await ctx.get(BASE_URL);\n  34 |     const html = await resp.text();\n> 35 |     expect(html).toContain('id=\"f-phase\"');\n     |                  ^\n  36 |     expect(html).toContain('Phase');\n  37 |   });\n  38 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 35
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  33 |     const resp = await ctx.get(BASE_URL);\n  34 |     const html = await resp.text();\n> 35 |     expect(html).toContain('id=\"f-phase\"');\n     |                  ^\n  36 |     expect(html).toContain('Phase');\n  37 |   });\n  38 |\n    at /var/www/360lm/tests/sales.spec.js:35:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:29.637Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-cb94a-se-field-exists-in-job-form-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 35
                      }
                    },
                    {
                      "workerIndex": 725,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:35:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 35
                        },
                        "snippet": "  33 |     const resp = await ctx.get(BASE_URL);\n  34 |     const html = await resp.text();\n> 35 |     expect(html).toContain('id=\"f-phase\"');\n     |                  ^\n  36 |     expect(html).toContain('Phase');\n  37 |   });\n  38 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 35
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  33 |     const resp = await ctx.get(BASE_URL);\n  34 |     const html = await resp.text();\n> 35 |     expect(html).toContain('id=\"f-phase\"');\n     |                  ^\n  36 |     expect(html).toContain('Phase');\n  37 |   });\n  38 |\n    at /var/www/360lm/tests/sales.spec.js:35:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:30.623Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-cb94a-se-field-exists-in-job-form-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 35
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-ac0f27baa41e2aa80458",
              "file": "sales.spec.js",
              "line": 31,
              "column": 3
            },
            {
              "title": "Zone chips group exists in job form",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 726,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:43:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 43
                        },
                        "snippet": "  41 |     const resp = await ctx.get(BASE_URL);\n  42 |     const html = await resp.text();\n> 43 |     expect(html).toContain('id=\"f-zones-group\"');\n     |                  ^\n  44 |     expect(html).toContain('id=\"f-zones-chips\"');\n  45 |     expect(html).toContain('id=\"f-zones-hint\"');\n  46 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 43
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  41 |     const resp = await ctx.get(BASE_URL);\n  42 |     const html = await resp.text();\n> 43 |     expect(html).toContain('id=\"f-zones-group\"');\n     |                  ^\n  44 |     expect(html).toContain('id=\"f-zones-chips\"');\n  45 |     expect(html).toContain('id=\"f-zones-hint\"');\n  46 |   });\n    at /var/www/360lm/tests/sales.spec.js:43:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:31.698Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-b0034-ps-group-exists-in-job-form-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 43
                      }
                    },
                    {
                      "workerIndex": 727,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:43:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 43
                        },
                        "snippet": "  41 |     const resp = await ctx.get(BASE_URL);\n  42 |     const html = await resp.text();\n> 43 |     expect(html).toContain('id=\"f-zones-group\"');\n     |                  ^\n  44 |     expect(html).toContain('id=\"f-zones-chips\"');\n  45 |     expect(html).toContain('id=\"f-zones-hint\"');\n  46 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 43
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  41 |     const resp = await ctx.get(BASE_URL);\n  42 |     const html = await resp.text();\n> 43 |     expect(html).toContain('id=\"f-zones-group\"');\n     |                  ^\n  44 |     expect(html).toContain('id=\"f-zones-chips\"');\n  45 |     expect(html).toContain('id=\"f-zones-hint\"');\n  46 |   });\n    at /var/www/360lm/tests/sales.spec.js:43:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:32.691Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-b0034-ps-group-exists-in-job-form-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 43
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-2fb50a80064903095acd",
              "file": "sales.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "label says Generated Campaign ID not Job Name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 728,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:52:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 52
                        },
                        "snippet": "  50 |     const resp = await ctx.get(BASE_URL);\n  51 |     const html = await resp.text();\n> 52 |     expect(html).toContain('Generated Campaign ID');\n     |                  ^\n  53 |     expect(html).toContain('Campaign Identity');\n  54 |   });\n  55 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 52
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  50 |     const resp = await ctx.get(BASE_URL);\n  51 |     const html = await resp.text();\n> 52 |     expect(html).toContain('Generated Campaign ID');\n     |                  ^\n  53 |     expect(html).toContain('Campaign Identity');\n  54 |   });\n  55 |\n    at /var/www/360lm/tests/sales.spec.js:52:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:33.640Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-25491-ed-Campaign-ID-not-Job-Name-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 52
                      }
                    },
                    {
                      "workerIndex": 729,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:52:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 52
                        },
                        "snippet": "  50 |     const resp = await ctx.get(BASE_URL);\n  51 |     const html = await resp.text();\n> 52 |     expect(html).toContain('Generated Campaign ID');\n     |                  ^\n  53 |     expect(html).toContain('Campaign Identity');\n  54 |   });\n  55 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 52
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  50 |     const resp = await ctx.get(BASE_URL);\n  51 |     const html = await resp.text();\n> 52 |     expect(html).toContain('Generated Campaign ID');\n     |                  ^\n  53 |     expect(html).toContain('Campaign Identity');\n  54 |   });\n  55 |\n    at /var/www/360lm/tests/sales.spec.js:52:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:34.639Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-25491-ed-Campaign-ID-not-Job-Name-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 52
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-81024dc1d70ba049f887",
              "file": "sales.spec.js",
              "line": 48,
              "column": 3
            },
            {
              "title": "year placeholder is 2025 (4-digit)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 730,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 75,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:60:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 60
                        },
                        "snippet": "  58 |     const resp = await ctx.get(BASE_URL);\n  59 |     const html = await resp.text();\n> 60 |     expect(html).toContain('placeholder=\"2025\"');\n     |                  ^\n  61 |   });\n  62 | });\n  63 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  58 |     const resp = await ctx.get(BASE_URL);\n  59 |     const html = await resp.text();\n> 60 |     expect(html).toContain('placeholder=\"2025\"');\n     |                  ^\n  61 |   });\n  62 | });\n  63 |\n    at /var/www/360lm/tests/sales.spec.js:60:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:35.573Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-a6974-laceholder-is-2025-4-digit--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 60
                      }
                    },
                    {
                      "workerIndex": 731,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:60:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 60
                        },
                        "snippet": "  58 |     const resp = await ctx.get(BASE_URL);\n  59 |     const html = await resp.text();\n> 60 |     expect(html).toContain('placeholder=\"2025\"');\n     |                  ^\n  61 |   });\n  62 | });\n  63 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  58 |     const resp = await ctx.get(BASE_URL);\n  59 |     const html = await resp.text();\n> 60 |     expect(html).toContain('placeholder=\"2025\"');\n     |                  ^\n  61 |   });\n  62 | });\n  63 |\n    at /var/www/360lm/tests/sales.spec.js:60:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:36.526Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-a6974-laceholder-is-2025-4-digit--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 60
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-f12480b5354768d073e0",
              "file": "sales.spec.js",
              "line": 56,
              "column": 3
            },
            {
              "title": "Phase field exists in job form",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1732,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:35:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 35
                        },
                        "snippet": "  33 |     const resp = await ctx.get(BASE_URL);\n  34 |     const html = await resp.text();\n> 35 |     expect(html).toContain('id=\"f-phase\"');\n     |                  ^\n  36 |     expect(html).toContain('Phase');\n  37 |   });\n  38 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 35
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  33 |     const resp = await ctx.get(BASE_URL);\n  34 |     const html = await resp.text();\n> 35 |     expect(html).toContain('id=\"f-phase\"');\n     |                  ^\n  36 |     expect(html).toContain('Phase');\n  37 |   });\n  38 |\n    at /var/www/360lm/tests/sales.spec.js:35:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:33.427Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-cb94a-se-field-exists-in-job-form-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 35
                      }
                    },
                    {
                      "workerIndex": 1733,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:35:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 35
                        },
                        "snippet": "  33 |     const resp = await ctx.get(BASE_URL);\n  34 |     const html = await resp.text();\n> 35 |     expect(html).toContain('id=\"f-phase\"');\n     |                  ^\n  36 |     expect(html).toContain('Phase');\n  37 |   });\n  38 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 35
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-phase\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  33 |     const resp = await ctx.get(BASE_URL);\n  34 |     const html = await resp.text();\n> 35 |     expect(html).toContain('id=\"f-phase\"');\n     |                  ^\n  36 |     expect(html).toContain('Phase');\n  37 |   });\n  38 |\n    at /var/www/360lm/tests/sales.spec.js:35:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:34.330Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-cb94a-se-field-exists-in-job-form-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 35
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-b3aff5e89bc9d04fb6c9",
              "file": "sales.spec.js",
              "line": 31,
              "column": 3
            },
            {
              "title": "Zone chips group exists in job form",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1734,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:43:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 43
                        },
                        "snippet": "  41 |     const resp = await ctx.get(BASE_URL);\n  42 |     const html = await resp.text();\n> 43 |     expect(html).toContain('id=\"f-zones-group\"');\n     |                  ^\n  44 |     expect(html).toContain('id=\"f-zones-chips\"');\n  45 |     expect(html).toContain('id=\"f-zones-hint\"');\n  46 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 43
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  41 |     const resp = await ctx.get(BASE_URL);\n  42 |     const html = await resp.text();\n> 43 |     expect(html).toContain('id=\"f-zones-group\"');\n     |                  ^\n  44 |     expect(html).toContain('id=\"f-zones-chips\"');\n  45 |     expect(html).toContain('id=\"f-zones-hint\"');\n  46 |   });\n    at /var/www/360lm/tests/sales.spec.js:43:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:35.239Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-b0034-ps-group-exists-in-job-form-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 43
                      }
                    },
                    {
                      "workerIndex": 1735,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:43:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 43
                        },
                        "snippet": "  41 |     const resp = await ctx.get(BASE_URL);\n  42 |     const html = await resp.text();\n> 43 |     expect(html).toContain('id=\"f-zones-group\"');\n     |                  ^\n  44 |     expect(html).toContain('id=\"f-zones-chips\"');\n  45 |     expect(html).toContain('id=\"f-zones-hint\"');\n  46 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 43
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-zones-group\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  41 |     const resp = await ctx.get(BASE_URL);\n  42 |     const html = await resp.text();\n> 43 |     expect(html).toContain('id=\"f-zones-group\"');\n     |                  ^\n  44 |     expect(html).toContain('id=\"f-zones-chips\"');\n  45 |     expect(html).toContain('id=\"f-zones-hint\"');\n  46 |   });\n    at /var/www/360lm/tests/sales.spec.js:43:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:36.160Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-b0034-ps-group-exists-in-job-form-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 43
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-f00070a72d28734088d3",
              "file": "sales.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "label says Generated Campaign ID not Job Name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1736,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:52:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 52
                        },
                        "snippet": "  50 |     const resp = await ctx.get(BASE_URL);\n  51 |     const html = await resp.text();\n> 52 |     expect(html).toContain('Generated Campaign ID');\n     |                  ^\n  53 |     expect(html).toContain('Campaign Identity');\n  54 |   });\n  55 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 52
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  50 |     const resp = await ctx.get(BASE_URL);\n  51 |     const html = await resp.text();\n> 52 |     expect(html).toContain('Generated Campaign ID');\n     |                  ^\n  53 |     expect(html).toContain('Campaign Identity');\n  54 |   });\n  55 |\n    at /var/www/360lm/tests/sales.spec.js:52:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:37.063Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-25491-ed-Campaign-ID-not-Job-Name-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 52
                      }
                    },
                    {
                      "workerIndex": 1737,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 67,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:52:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 52
                        },
                        "snippet": "  50 |     const resp = await ctx.get(BASE_URL);\n  51 |     const html = await resp.text();\n> 52 |     expect(html).toContain('Generated Campaign ID');\n     |                  ^\n  53 |     expect(html).toContain('Campaign Identity');\n  54 |   });\n  55 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 52
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Generated Campaign ID\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  50 |     const resp = await ctx.get(BASE_URL);\n  51 |     const html = await resp.text();\n> 52 |     expect(html).toContain('Generated Campaign ID');\n     |                  ^\n  53 |     expect(html).toContain('Campaign Identity');\n  54 |   });\n  55 |\n    at /var/www/360lm/tests/sales.spec.js:52:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:37.968Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-25491-ed-Campaign-ID-not-Job-Name-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 52
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-1e569f4c57cf21d8d1ed",
              "file": "sales.spec.js",
              "line": 48,
              "column": 3
            },
            {
              "title": "year placeholder is 2025 (4-digit)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1738,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:60:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 60
                        },
                        "snippet": "  58 |     const resp = await ctx.get(BASE_URL);\n  59 |     const html = await resp.text();\n> 60 |     expect(html).toContain('placeholder=\"2025\"');\n     |                  ^\n  61 |   });\n  62 | });\n  63 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  58 |     const resp = await ctx.get(BASE_URL);\n  59 |     const html = await resp.text();\n> 60 |     expect(html).toContain('placeholder=\"2025\"');\n     |                  ^\n  61 |   });\n  62 | });\n  63 |\n    at /var/www/360lm/tests/sales.spec.js:60:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:38.895Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-a6974-laceholder-is-2025-4-digit--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 60
                      }
                    },
                    {
                      "workerIndex": 1739,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:60:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 60
                        },
                        "snippet": "  58 |     const resp = await ctx.get(BASE_URL);\n  59 |     const html = await resp.text();\n> 60 |     expect(html).toContain('placeholder=\"2025\"');\n     |                  ^\n  61 |   });\n  62 | });\n  63 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 60
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"placeholder=\\\"2025\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  58 |     const resp = await ctx.get(BASE_URL);\n  59 |     const html = await resp.text();\n> 60 |     expect(html).toContain('placeholder=\"2025\"');\n     |                  ^\n  61 |   });\n  62 | });\n  63 |\n    at /var/www/360lm/tests/sales.spec.js:60:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:39.817Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Canonical-Jo-a6974-laceholder-is-2025-4-digit--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 60
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-3525d1db881808a9c5d9",
              "file": "sales.spec.js",
              "line": 56,
              "column": 3
            }
          ]
        },
        {
          "title": "Sales — Single-Step Job Creation",
          "file": "sales.spec.js",
          "line": 65,
          "column": 6,
          "specs": [
            {
              "title": "source no longer references installHdr/installation profile (ADR-111)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 732,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 62,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:37.506Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-5ad8cf624b5f4d5404df",
              "file": "sales.spec.js",
              "line": 66,
              "column": 3
            },
            {
              "title": "source contains campaignSlug and buildCampaignIdStr helpers",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 732,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 16,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:77:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 77
                        },
                        "snippet": "  75 |     const resp = await ctx.get(BASE_URL);\n  76 |     const src = await resp.text();\n> 77 |     expect(src).toContain('campaignSlug');\n     |                 ^\n  78 |     expect(src).toContain('buildCampaignIdStr');\n  79 |   });\n  80 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 77
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  75 |     const resp = await ctx.get(BASE_URL);\n  76 |     const src = await resp.text();\n> 77 |     expect(src).toContain('campaignSlug');\n     |                 ^\n  78 |     expect(src).toContain('buildCampaignIdStr');\n  79 |   });\n  80 |\n    at /var/www/360lm/tests/sales.spec.js:77:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:37.623Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--9f68f--buildCampaignIdStr-helpers-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 77
                      }
                    },
                    {
                      "workerIndex": 733,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:77:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 77
                        },
                        "snippet": "  75 |     const resp = await ctx.get(BASE_URL);\n  76 |     const src = await resp.text();\n> 77 |     expect(src).toContain('campaignSlug');\n     |                 ^\n  78 |     expect(src).toContain('buildCampaignIdStr');\n  79 |   });\n  80 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 77
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  75 |     const resp = await ctx.get(BASE_URL);\n  76 |     const src = await resp.text();\n> 77 |     expect(src).toContain('campaignSlug');\n     |                 ^\n  78 |     expect(src).toContain('buildCampaignIdStr');\n  79 |   });\n  80 |\n    at /var/www/360lm/tests/sales.spec.js:77:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:38.520Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--9f68f--buildCampaignIdStr-helpers-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 77
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-da54a25dfc32f65a85c8",
              "file": "sales.spec.js",
              "line": 73,
              "column": 3
            },
            {
              "title": "source writes new jobs directly to /db/jobs with operational_draft status",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 734,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:85:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 85
                        },
                        "snippet": "  83 |     const resp = await ctx.get(BASE_URL);\n  84 |     const src = await resp.text();\n> 85 |     expect(src).toContain(\"fetch(`${CFG.api}/jobs`\");\n     |                 ^\n  86 |     expect(src).toContain(\"'operational_draft'\");\n  87 |   });\n  88 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 85
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  83 |     const resp = await ctx.get(BASE_URL);\n  84 |     const src = await resp.text();\n> 85 |     expect(src).toContain(\"fetch(`${CFG.api}/jobs`\");\n     |                 ^\n  86 |     expect(src).toContain(\"'operational_draft'\");\n  87 |   });\n  88 |\n    at /var/www/360lm/tests/sales.spec.js:85:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:39.471Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--4e858-th-operational-draft-status-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 85
                      }
                    },
                    {
                      "workerIndex": 735,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:85:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 85
                        },
                        "snippet": "  83 |     const resp = await ctx.get(BASE_URL);\n  84 |     const src = await resp.text();\n> 85 |     expect(src).toContain(\"fetch(`${CFG.api}/jobs`\");\n     |                 ^\n  86 |     expect(src).toContain(\"'operational_draft'\");\n  87 |   });\n  88 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 85
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  83 |     const resp = await ctx.get(BASE_URL);\n  84 |     const src = await resp.text();\n> 85 |     expect(src).toContain(\"fetch(`${CFG.api}/jobs`\");\n     |                 ^\n  86 |     expect(src).toContain(\"'operational_draft'\");\n  87 |   });\n  88 |\n    at /var/www/360lm/tests/sales.spec.js:85:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:40.393Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--4e858-th-operational-draft-status-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 85
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-d89a8a0db4ae3605e5bc",
              "file": "sales.spec.js",
              "line": 81,
              "column": 3
            },
            {
              "title": "source writes campaign_id to sales.jobs",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 736,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:93:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 93
                        },
                        "snippet": "  91 |     const resp = await ctx.get(BASE_URL);\n  92 |     const src = await resp.text();\n> 93 |     expect(src).toContain('campaign_id:   campaignId');\n     |                 ^\n  94 |     expect(src).toContain('job_name:      campaignId');\n  95 |   });\n  96 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 93
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  91 |     const resp = await ctx.get(BASE_URL);\n  92 |     const src = await resp.text();\n> 93 |     expect(src).toContain('campaign_id:   campaignId');\n     |                 ^\n  94 |     expect(src).toContain('job_name:      campaignId');\n  95 |   });\n  96 |\n    at /var/www/360lm/tests/sales.spec.js:93:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:41.402Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--e21ad-s-campaign-id-to-sales-jobs-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 93
                      }
                    },
                    {
                      "workerIndex": 737,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 67,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:93:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 93
                        },
                        "snippet": "  91 |     const resp = await ctx.get(BASE_URL);\n  92 |     const src = await resp.text();\n> 93 |     expect(src).toContain('campaign_id:   campaignId');\n     |                 ^\n  94 |     expect(src).toContain('job_name:      campaignId');\n  95 |   });\n  96 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 93
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  91 |     const resp = await ctx.get(BASE_URL);\n  92 |     const src = await resp.text();\n> 93 |     expect(src).toContain('campaign_id:   campaignId');\n     |                 ^\n  94 |     expect(src).toContain('job_name:      campaignId');\n  95 |   });\n  96 |\n    at /var/www/360lm/tests/sales.spec.js:93:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:42.367Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--e21ad-s-campaign-id-to-sales-jobs-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 93
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-d93331224d0314824ff4",
              "file": "sales.spec.js",
              "line": 89,
              "column": 3
            },
            {
              "title": "source has app-layer rollback on Step 2 fail",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 738,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:101:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 101
                        },
                        "snippet": "   99 |     const resp = await ctx.get(BASE_URL);\n  100 |     const src = await resp.text();\n> 101 |     expect(src).toContain('App-layer rollback');\n      |                 ^\n  102 |     expect(src).toContain(\"method:'DELETE'\");\n  103 |     // Duplicate campaign_id gives clear error message\n  104 |     expect(src).toContain('23505');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 101
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n   99 |     const resp = await ctx.get(BASE_URL);\n  100 |     const src = await resp.text();\n> 101 |     expect(src).toContain('App-layer rollback');\n      |                 ^\n  102 |     expect(src).toContain(\"method:'DELETE'\");\n  103 |     // Duplicate campaign_id gives clear error message\n  104 |     expect(src).toContain('23505');\n    at /var/www/360lm/tests/sales.spec.js:101:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:43.331Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--a9676-yer-rollback-on-Step-2-fail-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 101
                      }
                    },
                    {
                      "workerIndex": 739,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:101:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 101
                        },
                        "snippet": "   99 |     const resp = await ctx.get(BASE_URL);\n  100 |     const src = await resp.text();\n> 101 |     expect(src).toContain('App-layer rollback');\n      |                 ^\n  102 |     expect(src).toContain(\"method:'DELETE'\");\n  103 |     // Duplicate campaign_id gives clear error message\n  104 |     expect(src).toContain('23505');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 101
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n   99 |     const resp = await ctx.get(BASE_URL);\n  100 |     const src = await resp.text();\n> 101 |     expect(src).toContain('App-layer rollback');\n      |                 ^\n  102 |     expect(src).toContain(\"method:'DELETE'\");\n  103 |     // Duplicate campaign_id gives clear error message\n  104 |     expect(src).toContain('23505');\n    at /var/www/360lm/tests/sales.spec.js:101:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:44.170Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--a9676-yer-rollback-on-Step-2-fail-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 101
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-889265931b58343bfcb9",
              "file": "sales.spec.js",
              "line": 97,
              "column": 3
            },
            {
              "title": "source contains zone state and loadZoneChips",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 740,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:112:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 112
                        },
                        "snippet": "  110 |     const resp = await ctx.get(BASE_URL);\n  111 |     const src = await resp.text();\n> 112 |     expect(src).toContain('_selectedZones');\n      |                 ^\n  113 |     expect(src).toContain('loadZoneChips');\n  114 |     expect(src).toContain('toggleZone');\n  115 |     expect(src).toContain('renderZoneChips');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 112
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  110 |     const resp = await ctx.get(BASE_URL);\n  111 |     const src = await resp.text();\n> 112 |     expect(src).toContain('_selectedZones');\n      |                 ^\n  113 |     expect(src).toContain('loadZoneChips');\n  114 |     expect(src).toContain('toggleZone');\n  115 |     expect(src).toContain('renderZoneChips');\n    at /var/www/360lm/tests/sales.spec.js:112:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:45.021Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--6bcf1-one-state-and-loadZoneChips-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 112
                      }
                    },
                    {
                      "workerIndex": 741,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:112:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 112
                        },
                        "snippet": "  110 |     const resp = await ctx.get(BASE_URL);\n  111 |     const src = await resp.text();\n> 112 |     expect(src).toContain('_selectedZones');\n      |                 ^\n  113 |     expect(src).toContain('loadZoneChips');\n  114 |     expect(src).toContain('toggleZone');\n  115 |     expect(src).toContain('renderZoneChips');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 112
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  110 |     const resp = await ctx.get(BASE_URL);\n  111 |     const src = await resp.text();\n> 112 |     expect(src).toContain('_selectedZones');\n      |                 ^\n  113 |     expect(src).toContain('loadZoneChips');\n  114 |     expect(src).toContain('toggleZone');\n  115 |     expect(src).toContain('renderZoneChips');\n    at /var/www/360lm/tests/sales.spec.js:112:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:45.846Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--6bcf1-one-state-and-loadZoneChips-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 112
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-42b356a4eb8c1825fb4a",
              "file": "sales.spec.js",
              "line": 108,
              "column": 3
            },
            {
              "title": "zone selection is required (validation in saveJob)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 742,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:122:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 122
                        },
                        "snippet": "  120 |     const resp = await ctx.get(BASE_URL);\n  121 |     const src = await resp.text();\n> 122 |     expect(src).toContain('Select at least one zone');\n      |                 ^\n  123 |   });\n  124 |\n  125 |   test('edit mode locks campaign identity fields', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 122
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  120 |     const resp = await ctx.get(BASE_URL);\n  121 |     const src = await resp.text();\n> 122 |     expect(src).toContain('Select at least one zone');\n      |                 ^\n  123 |   });\n  124 |\n  125 |   test('edit mode locks campaign identity fields', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:122:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:46.689Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--39f33-ired-validation-in-saveJob--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 122
                      }
                    },
                    {
                      "workerIndex": 743,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:122:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 122
                        },
                        "snippet": "  120 |     const resp = await ctx.get(BASE_URL);\n  121 |     const src = await resp.text();\n> 122 |     expect(src).toContain('Select at least one zone');\n      |                 ^\n  123 |   });\n  124 |\n  125 |   test('edit mode locks campaign identity fields', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 122
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  120 |     const resp = await ctx.get(BASE_URL);\n  121 |     const src = await resp.text();\n> 122 |     expect(src).toContain('Select at least one zone');\n      |                 ^\n  123 |   });\n  124 |\n  125 |   test('edit mode locks campaign identity fields', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:122:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:47.550Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--39f33-ired-validation-in-saveJob--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 122
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-1d3f21c76456457a441f",
              "file": "sales.spec.js",
              "line": 118,
              "column": 3
            },
            {
              "title": "edit mode locks campaign identity fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 744,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:129:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 129
                        },
                        "snippet": "  127 |     const resp = await ctx.get(BASE_URL);\n  128 |     const src = await resp.text();\n> 129 |     expect(src).toContain(\"'f-year','f-phase','f-brand','f-client','f-campaign'\");\n      |                 ^\n  130 |     expect(src).toContain('el.disabled = locked');\n  131 |     expect(src).toContain('zonesGroup.style.display = \\'none\\'');\n  132 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 129
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  127 |     const resp = await ctx.get(BASE_URL);\n  128 |     const src = await resp.text();\n> 129 |     expect(src).toContain(\"'f-year','f-phase','f-brand','f-client','f-campaign'\");\n      |                 ^\n  130 |     expect(src).toContain('el.disabled = locked');\n  131 |     expect(src).toContain('zonesGroup.style.display = \\'none\\'');\n  132 |   });\n    at /var/www/360lm/tests/sales.spec.js:129:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:48.497Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--c4a60-ks-campaign-identity-fields-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 129
                      }
                    },
                    {
                      "workerIndex": 745,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 84,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:129:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 129
                        },
                        "snippet": "  127 |     const resp = await ctx.get(BASE_URL);\n  128 |     const src = await resp.text();\n> 129 |     expect(src).toContain(\"'f-year','f-phase','f-brand','f-client','f-campaign'\");\n      |                 ^\n  130 |     expect(src).toContain('el.disabled = locked');\n  131 |     expect(src).toContain('zonesGroup.style.display = \\'none\\'');\n  132 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 129
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  127 |     const resp = await ctx.get(BASE_URL);\n  128 |     const src = await resp.text();\n> 129 |     expect(src).toContain(\"'f-year','f-phase','f-brand','f-client','f-campaign'\");\n      |                 ^\n  130 |     expect(src).toContain('el.disabled = locked');\n  131 |     expect(src).toContain('zonesGroup.style.display = \\'none\\'');\n  132 |   });\n    at /var/www/360lm/tests/sales.spec.js:129:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:49.525Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--c4a60-ks-campaign-identity-fields-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 129
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-949542a1c101aaf434cb",
              "file": "sales.spec.js",
              "line": 125,
              "column": 3
            },
            {
              "title": "source no longer references installHdr/installation profile (ADR-111)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1740,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 54,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:40.697Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-39b93cd4c6387331fbef",
              "file": "sales.spec.js",
              "line": 66,
              "column": 3
            },
            {
              "title": "source contains campaignSlug and buildCampaignIdStr helpers",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1740,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 23,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:77:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 77
                        },
                        "snippet": "  75 |     const resp = await ctx.get(BASE_URL);\n  76 |     const src = await resp.text();\n> 77 |     expect(src).toContain('campaignSlug');\n     |                 ^\n  78 |     expect(src).toContain('buildCampaignIdStr');\n  79 |   });\n  80 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 77
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  75 |     const resp = await ctx.get(BASE_URL);\n  76 |     const src = await resp.text();\n> 77 |     expect(src).toContain('campaignSlug');\n     |                 ^\n  78 |     expect(src).toContain('buildCampaignIdStr');\n  79 |   });\n  80 |\n    at /var/www/360lm/tests/sales.spec.js:77:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:40.799Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--9f68f--buildCampaignIdStr-helpers-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 77
                      }
                    },
                    {
                      "workerIndex": 1741,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 65,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:77:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 77
                        },
                        "snippet": "  75 |     const resp = await ctx.get(BASE_URL);\n  76 |     const src = await resp.text();\n> 77 |     expect(src).toContain('campaignSlug');\n     |                 ^\n  78 |     expect(src).toContain('buildCampaignIdStr');\n  79 |   });\n  80 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 77
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaignSlug\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  75 |     const resp = await ctx.get(BASE_URL);\n  76 |     const src = await resp.text();\n> 77 |     expect(src).toContain('campaignSlug');\n     |                 ^\n  78 |     expect(src).toContain('buildCampaignIdStr');\n  79 |   });\n  80 |\n    at /var/www/360lm/tests/sales.spec.js:77:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:41.680Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--9f68f--buildCampaignIdStr-helpers-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 77
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-3bfbdb2dec9ef189ce02",
              "file": "sales.spec.js",
              "line": 73,
              "column": 3
            },
            {
              "title": "source writes new jobs directly to /db/jobs with operational_draft status",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1742,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:85:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 85
                        },
                        "snippet": "  83 |     const resp = await ctx.get(BASE_URL);\n  84 |     const src = await resp.text();\n> 85 |     expect(src).toContain(\"fetch(`${CFG.api}/jobs`\");\n     |                 ^\n  86 |     expect(src).toContain(\"'operational_draft'\");\n  87 |   });\n  88 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 85
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  83 |     const resp = await ctx.get(BASE_URL);\n  84 |     const src = await resp.text();\n> 85 |     expect(src).toContain(\"fetch(`${CFG.api}/jobs`\");\n     |                 ^\n  86 |     expect(src).toContain(\"'operational_draft'\");\n  87 |   });\n  88 |\n    at /var/www/360lm/tests/sales.spec.js:85:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:42.576Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--4e858-th-operational-draft-status-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 85
                      }
                    },
                    {
                      "workerIndex": 1743,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 49,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:85:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 85
                        },
                        "snippet": "  83 |     const resp = await ctx.get(BASE_URL);\n  84 |     const src = await resp.text();\n> 85 |     expect(src).toContain(\"fetch(`${CFG.api}/jobs`\");\n     |                 ^\n  86 |     expect(src).toContain(\"'operational_draft'\");\n  87 |   });\n  88 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 85
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"fetch(`${CFG.api}/jobs`\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  83 |     const resp = await ctx.get(BASE_URL);\n  84 |     const src = await resp.text();\n> 85 |     expect(src).toContain(\"fetch(`${CFG.api}/jobs`\");\n     |                 ^\n  86 |     expect(src).toContain(\"'operational_draft'\");\n  87 |   });\n  88 |\n    at /var/www/360lm/tests/sales.spec.js:85:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:43.388Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--4e858-th-operational-draft-status-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 85
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-6a99224cf0b3485ce212",
              "file": "sales.spec.js",
              "line": 81,
              "column": 3
            },
            {
              "title": "source writes campaign_id to sales.jobs",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1744,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:93:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 93
                        },
                        "snippet": "  91 |     const resp = await ctx.get(BASE_URL);\n  92 |     const src = await resp.text();\n> 93 |     expect(src).toContain('campaign_id:   campaignId');\n     |                 ^\n  94 |     expect(src).toContain('job_name:      campaignId');\n  95 |   });\n  96 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 93
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  91 |     const resp = await ctx.get(BASE_URL);\n  92 |     const src = await resp.text();\n> 93 |     expect(src).toContain('campaign_id:   campaignId');\n     |                 ^\n  94 |     expect(src).toContain('job_name:      campaignId');\n  95 |   });\n  96 |\n    at /var/www/360lm/tests/sales.spec.js:93:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:44.258Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--e21ad-s-campaign-id-to-sales-jobs-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 93
                      }
                    },
                    {
                      "workerIndex": 1745,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:93:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 93
                        },
                        "snippet": "  91 |     const resp = await ctx.get(BASE_URL);\n  92 |     const src = await resp.text();\n> 93 |     expect(src).toContain('campaign_id:   campaignId');\n     |                 ^\n  94 |     expect(src).toContain('job_name:      campaignId');\n  95 |   });\n  96 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 93
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"campaign_id:   campaignId\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  91 |     const resp = await ctx.get(BASE_URL);\n  92 |     const src = await resp.text();\n> 93 |     expect(src).toContain('campaign_id:   campaignId');\n     |                 ^\n  94 |     expect(src).toContain('job_name:      campaignId');\n  95 |   });\n  96 |\n    at /var/www/360lm/tests/sales.spec.js:93:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:45.087Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--e21ad-s-campaign-id-to-sales-jobs-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 93
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-cd2f1d12bc4731dda73a",
              "file": "sales.spec.js",
              "line": 89,
              "column": 3
            },
            {
              "title": "source has app-layer rollback on Step 2 fail",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1746,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 49,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:101:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 101
                        },
                        "snippet": "   99 |     const resp = await ctx.get(BASE_URL);\n  100 |     const src = await resp.text();\n> 101 |     expect(src).toContain('App-layer rollback');\n      |                 ^\n  102 |     expect(src).toContain(\"method:'DELETE'\");\n  103 |     // Duplicate campaign_id gives clear error message\n  104 |     expect(src).toContain('23505');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 101
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n   99 |     const resp = await ctx.get(BASE_URL);\n  100 |     const src = await resp.text();\n> 101 |     expect(src).toContain('App-layer rollback');\n      |                 ^\n  102 |     expect(src).toContain(\"method:'DELETE'\");\n  103 |     // Duplicate campaign_id gives clear error message\n  104 |     expect(src).toContain('23505');\n    at /var/www/360lm/tests/sales.spec.js:101:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:45.902Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--a9676-yer-rollback-on-Step-2-fail-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 101
                      }
                    },
                    {
                      "workerIndex": 1747,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 48,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:101:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 101
                        },
                        "snippet": "   99 |     const resp = await ctx.get(BASE_URL);\n  100 |     const src = await resp.text();\n> 101 |     expect(src).toContain('App-layer rollback');\n      |                 ^\n  102 |     expect(src).toContain(\"method:'DELETE'\");\n  103 |     // Duplicate campaign_id gives clear error message\n  104 |     expect(src).toContain('23505');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 101
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"App-layer rollback\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n   99 |     const resp = await ctx.get(BASE_URL);\n  100 |     const src = await resp.text();\n> 101 |     expect(src).toContain('App-layer rollback');\n      |                 ^\n  102 |     expect(src).toContain(\"method:'DELETE'\");\n  103 |     // Duplicate campaign_id gives clear error message\n  104 |     expect(src).toContain('23505');\n    at /var/www/360lm/tests/sales.spec.js:101:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:46.719Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--a9676-yer-rollback-on-Step-2-fail-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 101
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-d88ae2e537546803314d",
              "file": "sales.spec.js",
              "line": 97,
              "column": 3
            },
            {
              "title": "source contains zone state and loadZoneChips",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1748,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 51,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:112:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 112
                        },
                        "snippet": "  110 |     const resp = await ctx.get(BASE_URL);\n  111 |     const src = await resp.text();\n> 112 |     expect(src).toContain('_selectedZones');\n      |                 ^\n  113 |     expect(src).toContain('loadZoneChips');\n  114 |     expect(src).toContain('toggleZone');\n  115 |     expect(src).toContain('renderZoneChips');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 112
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  110 |     const resp = await ctx.get(BASE_URL);\n  111 |     const src = await resp.text();\n> 112 |     expect(src).toContain('_selectedZones');\n      |                 ^\n  113 |     expect(src).toContain('loadZoneChips');\n  114 |     expect(src).toContain('toggleZone');\n  115 |     expect(src).toContain('renderZoneChips');\n    at /var/www/360lm/tests/sales.spec.js:112:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:47.548Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--6bcf1-one-state-and-loadZoneChips-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 112
                      }
                    },
                    {
                      "workerIndex": 1749,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:112:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 112
                        },
                        "snippet": "  110 |     const resp = await ctx.get(BASE_URL);\n  111 |     const src = await resp.text();\n> 112 |     expect(src).toContain('_selectedZones');\n      |                 ^\n  113 |     expect(src).toContain('loadZoneChips');\n  114 |     expect(src).toContain('toggleZone');\n  115 |     expect(src).toContain('renderZoneChips');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 112
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"_selectedZones\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  110 |     const resp = await ctx.get(BASE_URL);\n  111 |     const src = await resp.text();\n> 112 |     expect(src).toContain('_selectedZones');\n      |                 ^\n  113 |     expect(src).toContain('loadZoneChips');\n  114 |     expect(src).toContain('toggleZone');\n  115 |     expect(src).toContain('renderZoneChips');\n    at /var/www/360lm/tests/sales.spec.js:112:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:48.379Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--6bcf1-one-state-and-loadZoneChips-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 112
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-1464811f6c969c7f4a82",
              "file": "sales.spec.js",
              "line": 108,
              "column": 3
            },
            {
              "title": "zone selection is required (validation in saveJob)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1750,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:122:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 122
                        },
                        "snippet": "  120 |     const resp = await ctx.get(BASE_URL);\n  121 |     const src = await resp.text();\n> 122 |     expect(src).toContain('Select at least one zone');\n      |                 ^\n  123 |   });\n  124 |\n  125 |   test('edit mode locks campaign identity fields', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 122
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  120 |     const resp = await ctx.get(BASE_URL);\n  121 |     const src = await resp.text();\n> 122 |     expect(src).toContain('Select at least one zone');\n      |                 ^\n  123 |   });\n  124 |\n  125 |   test('edit mode locks campaign identity fields', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:122:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:49.230Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--39f33-ired-validation-in-saveJob--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 122
                      }
                    },
                    {
                      "workerIndex": 1751,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:122:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 122
                        },
                        "snippet": "  120 |     const resp = await ctx.get(BASE_URL);\n  121 |     const src = await resp.text();\n> 122 |     expect(src).toContain('Select at least one zone');\n      |                 ^\n  123 |   });\n  124 |\n  125 |   test('edit mode locks campaign identity fields', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 122
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Select at least one zone\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  120 |     const resp = await ctx.get(BASE_URL);\n  121 |     const src = await resp.text();\n> 122 |     expect(src).toContain('Select at least one zone');\n      |                 ^\n  123 |   });\n  124 |\n  125 |   test('edit mode locks campaign identity fields', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:122:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:50.079Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--39f33-ired-validation-in-saveJob--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 122
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-f1992f662fc4b77e691c",
              "file": "sales.spec.js",
              "line": 118,
              "column": 3
            },
            {
              "title": "edit mode locks campaign identity fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1752,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:129:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 129
                        },
                        "snippet": "  127 |     const resp = await ctx.get(BASE_URL);\n  128 |     const src = await resp.text();\n> 129 |     expect(src).toContain(\"'f-year','f-phase','f-brand','f-client','f-campaign'\");\n      |                 ^\n  130 |     expect(src).toContain('el.disabled = locked');\n  131 |     expect(src).toContain('zonesGroup.style.display = \\'none\\'');\n  132 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 129
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  127 |     const resp = await ctx.get(BASE_URL);\n  128 |     const src = await resp.text();\n> 129 |     expect(src).toContain(\"'f-year','f-phase','f-brand','f-client','f-campaign'\");\n      |                 ^\n  130 |     expect(src).toContain('el.disabled = locked');\n  131 |     expect(src).toContain('zonesGroup.style.display = \\'none\\'');\n  132 |   });\n    at /var/www/360lm/tests/sales.spec.js:129:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:51.016Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--c4a60-ks-campaign-identity-fields-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 129
                      }
                    },
                    {
                      "workerIndex": 1753,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:129:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 129
                        },
                        "snippet": "  127 |     const resp = await ctx.get(BASE_URL);\n  128 |     const src = await resp.text();\n> 129 |     expect(src).toContain(\"'f-year','f-phase','f-brand','f-client','f-campaign'\");\n      |                 ^\n  130 |     expect(src).toContain('el.disabled = locked');\n  131 |     expect(src).toContain('zonesGroup.style.display = \\'none\\'');\n  132 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 129
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"'f-year','f-phase','f-brand','f-client','f-campaign'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  127 |     const resp = await ctx.get(BASE_URL);\n  128 |     const src = await resp.text();\n> 129 |     expect(src).toContain(\"'f-year','f-phase','f-brand','f-client','f-campaign'\");\n      |                 ^\n  130 |     expect(src).toContain('el.disabled = locked');\n  131 |     expect(src).toContain('zonesGroup.style.display = \\'none\\'');\n  132 |   });\n    at /var/www/360lm/tests/sales.spec.js:129:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:51.940Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Single-Step--c4a60-ks-campaign-identity-fields-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 129
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-ee4b0e2bbd37f16ead0d",
              "file": "sales.spec.js",
              "line": 125,
              "column": 3
            }
          ]
        },
        {
          "title": "Hub PWA — Campaign Creation Removed (Step 4b)",
          "file": "sales.spec.js",
          "line": 136,
          "column": 6,
          "specs": [
            {
              "title": "hub sw.js contains a current version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 746,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 328,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:142:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 142
                        },
                        "snippet": "  140 |     const resp = await page.goto(HUB_URL + 'sw.js');\n  141 |     const text = await resp?.text() || '';\n> 142 |     expect(text).toMatch(/360lm-hub-v\\d+/);\n      |                  ^\n  143 |   });\n  144 |\n  145 |   test('hub source no longer contains modal-new-campaign form fields', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 142
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  140 |     const resp = await page.goto(HUB_URL + 'sw.js');\n  141 |     const text = await resp?.text() || '';\n> 142 |     expect(text).toMatch(/360lm-hub-v\\d+/);\n      |                  ^\n  143 |   });\n  144 |\n  145 |   test('hub source no longer contains modal-new-campaign form fields', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:142:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:50.645Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-2aac7--contains-a-current-version-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-2aac7--contains-a-current-version-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 142
                      }
                    },
                    {
                      "workerIndex": 747,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 289,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:142:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 142
                        },
                        "snippet": "  140 |     const resp = await page.goto(HUB_URL + 'sw.js');\n  141 |     const text = await resp?.text() || '';\n> 142 |     expect(text).toMatch(/360lm-hub-v\\d+/);\n      |                  ^\n  143 |   });\n  144 |\n  145 |   test('hub source no longer contains modal-new-campaign form fields', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 142
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  140 |     const resp = await page.goto(HUB_URL + 'sw.js');\n  141 |     const text = await resp?.text() || '';\n> 142 |     expect(text).toMatch(/360lm-hub-v\\d+/);\n      |                  ^\n  143 |   });\n  144 |\n  145 |   test('hub source no longer contains modal-new-campaign form fields', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:142:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:52.061Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-2aac7--contains-a-current-version-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-2aac7--contains-a-current-version-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 142
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-1c6a2e6300199365143f",
              "file": "sales.spec.js",
              "line": 139,
              "column": 3
            },
            {
              "title": "hub source no longer contains modal-new-campaign form fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 748,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:154:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 154
                        },
                        "snippet": "  152 |     expect(html).not.toContain('id=\"nc-campaign\"');\n  153 |     // Comment confirming removal should be present\n> 154 |     expect(html).toContain('Campaign creation moved to Sales PWA');\n      |                  ^\n  155 |   });\n  156 |\n  157 |   test('hub source has stub openNewCampaign redirecting to /sales/', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 154
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  152 |     expect(html).not.toContain('id=\"nc-campaign\"');\n  153 |     // Comment confirming removal should be present\n> 154 |     expect(html).toContain('Campaign creation moved to Sales PWA');\n      |                  ^\n  155 |   });\n  156 |\n  157 |   test('hub source has stub openNewCampaign redirecting to /sales/', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:154:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:53.370Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-4f4d1-al-new-campaign-form-fields-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 154
                      }
                    },
                    {
                      "workerIndex": 749,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:154:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 154
                        },
                        "snippet": "  152 |     expect(html).not.toContain('id=\"nc-campaign\"');\n  153 |     // Comment confirming removal should be present\n> 154 |     expect(html).toContain('Campaign creation moved to Sales PWA');\n      |                  ^\n  155 |   });\n  156 |\n  157 |   test('hub source has stub openNewCampaign redirecting to /sales/', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 154
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  152 |     expect(html).not.toContain('id=\"nc-campaign\"');\n  153 |     // Comment confirming removal should be present\n> 154 |     expect(html).toContain('Campaign creation moved to Sales PWA');\n      |                  ^\n  155 |   });\n  156 |\n  157 |   test('hub source has stub openNewCampaign redirecting to /sales/', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:154:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:54.236Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-4f4d1-al-new-campaign-form-fields-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 154
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-3f8919964aeb839afad2",
              "file": "sales.spec.js",
              "line": 145,
              "column": 3
            },
            {
              "title": "hub source has stub openNewCampaign redirecting to /sales/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 750,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:161:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 161
                        },
                        "snippet": "  159 |     const resp = await ctx.get(HUB_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain('function openNewCampaign()');\n      |                 ^\n  162 |     expect(src).toContain(\"window.location.href = '/sales/'\");\n  163 |   });\n  164 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 161
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  159 |     const resp = await ctx.get(HUB_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain('function openNewCampaign()');\n      |                 ^\n  162 |     expect(src).toContain(\"window.location.href = '/sales/'\");\n  163 |   });\n  164 | });\n    at /var/www/360lm/tests/sales.spec.js:161:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:55.071Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-e5132-paign-redirecting-to-sales--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 161
                      }
                    },
                    {
                      "workerIndex": 751,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:161:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 161
                        },
                        "snippet": "  159 |     const resp = await ctx.get(HUB_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain('function openNewCampaign()');\n      |                 ^\n  162 |     expect(src).toContain(\"window.location.href = '/sales/'\");\n  163 |   });\n  164 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 161
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  159 |     const resp = await ctx.get(HUB_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain('function openNewCampaign()');\n      |                 ^\n  162 |     expect(src).toContain(\"window.location.href = '/sales/'\");\n  163 |   });\n  164 | });\n    at /var/www/360lm/tests/sales.spec.js:161:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:55.891Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-e5132-paign-redirecting-to-sales--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 161
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-60b336a2df5e13cdfe78",
              "file": "sales.spec.js",
              "line": 157,
              "column": 3
            },
            {
              "title": "hub sw.js contains a current version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1754,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 290,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:142:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 142
                        },
                        "snippet": "  140 |     const resp = await page.goto(HUB_URL + 'sw.js');\n  141 |     const text = await resp?.text() || '';\n> 142 |     expect(text).toMatch(/360lm-hub-v\\d+/);\n      |                  ^\n  143 |   });\n  144 |\n  145 |   test('hub source no longer contains modal-new-campaign form fields', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 142
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  140 |     const resp = await page.goto(HUB_URL + 'sw.js');\n  141 |     const text = await resp?.text() || '';\n> 142 |     expect(text).toMatch(/360lm-hub-v\\d+/);\n      |                  ^\n  143 |   });\n  144 |\n  145 |   test('hub source no longer contains modal-new-campaign form fields', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:142:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:52.894Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-2aac7--contains-a-current-version-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-2aac7--contains-a-current-version-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 142
                      }
                    },
                    {
                      "workerIndex": 1755,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 287,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:142:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 142
                        },
                        "snippet": "  140 |     const resp = await page.goto(HUB_URL + 'sw.js');\n  141 |     const text = await resp?.text() || '';\n> 142 |     expect(text).toMatch(/360lm-hub-v\\d+/);\n      |                  ^\n  143 |   });\n  144 |\n  145 |   test('hub source no longer contains modal-new-campaign form fields', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 142
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360lm-hub-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  140 |     const resp = await page.goto(HUB_URL + 'sw.js');\n  141 |     const text = await resp?.text() || '';\n> 142 |     expect(text).toMatch(/360lm-hub-v\\d+/);\n      |                  ^\n  143 |   });\n  144 |\n  145 |   test('hub source no longer contains modal-new-campaign form fields', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:142:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:54.190Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-2aac7--contains-a-current-version-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-2aac7--contains-a-current-version-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 142
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-f5cbcb93cda3b3be3faa",
              "file": "sales.spec.js",
              "line": 139,
              "column": 3
            },
            {
              "title": "hub source no longer contains modal-new-campaign form fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1756,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:154:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 154
                        },
                        "snippet": "  152 |     expect(html).not.toContain('id=\"nc-campaign\"');\n  153 |     // Comment confirming removal should be present\n> 154 |     expect(html).toContain('Campaign creation moved to Sales PWA');\n      |                  ^\n  155 |   });\n  156 |\n  157 |   test('hub source has stub openNewCampaign redirecting to /sales/', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 154
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  152 |     expect(html).not.toContain('id=\"nc-campaign\"');\n  153 |     // Comment confirming removal should be present\n> 154 |     expect(html).toContain('Campaign creation moved to Sales PWA');\n      |                  ^\n  155 |   });\n  156 |\n  157 |   test('hub source has stub openNewCampaign redirecting to /sales/', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:154:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:55.494Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-4f4d1-al-new-campaign-form-fields-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 154
                      }
                    },
                    {
                      "workerIndex": 1757,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:154:18",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 18,
                          "line": 154
                        },
                        "snippet": "  152 |     expect(html).not.toContain('id=\"nc-campaign\"');\n  153 |     // Comment confirming removal should be present\n> 154 |     expect(html).toContain('Campaign creation moved to Sales PWA');\n      |                  ^\n  155 |   });\n  156 |\n  157 |   test('hub source has stub openNewCampaign redirecting to /sales/', async ({}) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 18,
                            "line": 154
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"Campaign creation moved to Sales PWA\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  152 |     expect(html).not.toContain('id=\"nc-campaign\"');\n  153 |     // Comment confirming removal should be present\n> 154 |     expect(html).toContain('Campaign creation moved to Sales PWA');\n      |                  ^\n  155 |   });\n  156 |\n  157 |   test('hub source has stub openNewCampaign redirecting to /sales/', async ({}) => {\n    at /var/www/360lm/tests/sales.spec.js:154:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:56.444Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-4f4d1-al-new-campaign-form-fields-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 18,
                        "line": 154
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-fb80ba38634819e79910",
              "file": "sales.spec.js",
              "line": 145,
              "column": 3
            },
            {
              "title": "hub source has stub openNewCampaign redirecting to /sales/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1758,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:161:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 161
                        },
                        "snippet": "  159 |     const resp = await ctx.get(HUB_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain('function openNewCampaign()');\n      |                 ^\n  162 |     expect(src).toContain(\"window.location.href = '/sales/'\");\n  163 |   });\n  164 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 161
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  159 |     const resp = await ctx.get(HUB_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain('function openNewCampaign()');\n      |                 ^\n  162 |     expect(src).toContain(\"window.location.href = '/sales/'\");\n  163 |   });\n  164 | });\n    at /var/www/360lm/tests/sales.spec.js:161:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:57.302Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-e5132-paign-redirecting-to-sales--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 161
                      }
                    },
                    {
                      "workerIndex": 1759,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:161:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 161
                        },
                        "snippet": "  159 |     const resp = await ctx.get(HUB_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain('function openNewCampaign()');\n      |                 ^\n  162 |     expect(src).toContain(\"window.location.href = '/sales/'\");\n  163 |   });\n  164 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 161
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openNewCampaign()\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  159 |     const resp = await ctx.get(HUB_URL);\n  160 |     const src = await resp.text();\n> 161 |     expect(src).toContain('function openNewCampaign()');\n      |                 ^\n  162 |     expect(src).toContain(\"window.location.href = '/sales/'\");\n  163 |   });\n  164 | });\n    at /var/www/360lm/tests/sales.spec.js:161:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:58.109Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Hub-PWA-—-Campaign-C-e5132-paign-redirecting-to-sales--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 161
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-fc1538922916d8d0528e",
              "file": "sales.spec.js",
              "line": 157,
              "column": 3
            }
          ]
        },
        {
          "title": "Sales — Proforma Invoice",
          "file": "sales.spec.js",
          "line": 167,
          "column": 6,
          "specs": [
            {
              "title": "source contains openProformaOptions function",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 752,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:172:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 172
                        },
                        "snippet": "  170 |     const src = await (await ctx.get(BASE_URL)).text();\n  171 |     await ctx.dispose();\n> 172 |     expect(src).toContain('function openProformaOptions(');\n      |                 ^\n  173 |     expect(src).toContain('function generateProforma(');\n  174 |     expect(src).toContain('function buildProformaHtml(');\n  175 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 172
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  170 |     const src = await (await ctx.get(BASE_URL)).text();\n  171 |     await ctx.dispose();\n> 172 |     expect(src).toContain('function openProformaOptions(');\n      |                 ^\n  173 |     expect(src).toContain('function generateProforma(');\n  174 |     expect(src).toContain('function buildProformaHtml(');\n  175 |   });\n    at /var/www/360lm/tests/sales.spec.js:172:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:56.841Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-9a7d3-penProformaOptions-function-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 172
                      }
                    },
                    {
                      "workerIndex": 753,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:172:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 172
                        },
                        "snippet": "  170 |     const src = await (await ctx.get(BASE_URL)).text();\n  171 |     await ctx.dispose();\n> 172 |     expect(src).toContain('function openProformaOptions(');\n      |                 ^\n  173 |     expect(src).toContain('function generateProforma(');\n  174 |     expect(src).toContain('function buildProformaHtml(');\n  175 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 172
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  170 |     const src = await (await ctx.get(BASE_URL)).text();\n  171 |     await ctx.dispose();\n> 172 |     expect(src).toContain('function openProformaOptions(');\n      |                 ^\n  173 |     expect(src).toContain('function generateProforma(');\n  174 |     expect(src).toContain('function buildProformaHtml(');\n  175 |   });\n    at /var/www/360lm/tests/sales.spec.js:172:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:57.730Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-9a7d3-penProformaOptions-function-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 172
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-ecb97df1ac13ab9ebc6b",
              "file": "sales.spec.js",
              "line": 168,
              "column": 3
            },
            {
              "title": "source does not contain \"coming soon\" placeholder for Proforma",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 754,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 79,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:58.644Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-4c13109a138168f53a75",
              "file": "sales.spec.js",
              "line": 177,
              "column": 3
            },
            {
              "title": "modal-proforma-opts exists in DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 754,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 31,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:188:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 188
                        },
                        "snippet": "  186 |     const src = await (await ctx.get(BASE_URL)).text();\n  187 |     await ctx.dispose();\n> 188 |     expect(src).toContain('id=\"modal-proforma-opts\"');\n      |                 ^\n  189 |     expect(src).toContain('id=\"proforma-advance-pct\"');\n  190 |   });\n  191 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 188
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  186 |     const src = await (await ctx.get(BASE_URL)).text();\n  187 |     await ctx.dispose();\n> 188 |     expect(src).toContain('id=\"modal-proforma-opts\"');\n      |                 ^\n  189 |     expect(src).toContain('id=\"proforma-advance-pct\"');\n  190 |   });\n  191 |\n    at /var/www/360lm/tests/sales.spec.js:188:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:50:58.774Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-0c974-proforma-opts-exists-in-DOM-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 188
                      }
                    },
                    {
                      "workerIndex": 755,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 53,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:188:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 188
                        },
                        "snippet": "  186 |     const src = await (await ctx.get(BASE_URL)).text();\n  187 |     await ctx.dispose();\n> 188 |     expect(src).toContain('id=\"modal-proforma-opts\"');\n      |                 ^\n  189 |     expect(src).toContain('id=\"proforma-advance-pct\"');\n  190 |   });\n  191 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 188
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  186 |     const src = await (await ctx.get(BASE_URL)).text();\n  187 |     await ctx.dispose();\n> 188 |     expect(src).toContain('id=\"modal-proforma-opts\"');\n      |                 ^\n  189 |     expect(src).toContain('id=\"proforma-advance-pct\"');\n  190 |   });\n  191 |\n    at /var/www/360lm/tests/sales.spec.js:188:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:50:59.686Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-0c974-proforma-opts-exists-in-DOM-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 188
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-60cb3e7f499e63ef4680",
              "file": "sales.spec.js",
              "line": 184,
              "column": 3
            },
            {
              "title": "clients list renders Proforma Invoice button",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 756,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 73,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:196:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 196
                        },
                        "snippet": "  194 |     const src = await (await ctx.get(BASE_URL)).text();\n  195 |     await ctx.dispose();\n> 196 |     expect(src).toContain(\"openProformaOptions('${o.offer_id}')\");\n      |                 ^\n  197 |     expect(src).toContain('Proforma Invoice');\n  198 |   });\n  199 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 196
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  194 |     const src = await (await ctx.get(BASE_URL)).text();\n  195 |     await ctx.dispose();\n> 196 |     expect(src).toContain(\"openProformaOptions('${o.offer_id}')\");\n      |                 ^\n  197 |     expect(src).toContain('Proforma Invoice');\n  198 |   });\n  199 | });\n    at /var/www/360lm/tests/sales.spec.js:196:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:51:00.609Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-a7d72-ers-Proforma-Invoice-button-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 196
                      }
                    },
                    {
                      "workerIndex": 757,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 201,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:196:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 196
                        },
                        "snippet": "  194 |     const src = await (await ctx.get(BASE_URL)).text();\n  195 |     await ctx.dispose();\n> 196 |     expect(src).toContain(\"openProformaOptions('${o.offer_id}')\");\n      |                 ^\n  197 |     expect(src).toContain('Proforma Invoice');\n  198 |   });\n  199 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 196
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  194 |     const src = await (await ctx.get(BASE_URL)).text();\n  195 |     await ctx.dispose();\n> 196 |     expect(src).toContain(\"openProformaOptions('${o.offer_id}')\");\n      |                 ^\n  197 |     expect(src).toContain('Proforma Invoice');\n  198 |   });\n  199 | });\n    at /var/www/360lm/tests/sales.spec.js:196:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:51:01.893Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-a7d72-ers-Proforma-Invoice-button-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 196
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-4b89543f74b82801eb06",
              "file": "sales.spec.js",
              "line": 192,
              "column": 3
            },
            {
              "title": "source contains openProformaOptions function",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1760,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:172:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 172
                        },
                        "snippet": "  170 |     const src = await (await ctx.get(BASE_URL)).text();\n  171 |     await ctx.dispose();\n> 172 |     expect(src).toContain('function openProformaOptions(');\n      |                 ^\n  173 |     expect(src).toContain('function generateProforma(');\n  174 |     expect(src).toContain('function buildProformaHtml(');\n  175 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 172
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  170 |     const src = await (await ctx.get(BASE_URL)).text();\n  171 |     await ctx.dispose();\n> 172 |     expect(src).toContain('function openProformaOptions(');\n      |                 ^\n  173 |     expect(src).toContain('function generateProforma(');\n  174 |     expect(src).toContain('function buildProformaHtml(');\n  175 |   });\n    at /var/www/360lm/tests/sales.spec.js:172:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:50:58.987Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-9a7d3-penProformaOptions-function-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 172
                      }
                    },
                    {
                      "workerIndex": 1761,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:172:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 172
                        },
                        "snippet": "  170 |     const src = await (await ctx.get(BASE_URL)).text();\n  171 |     await ctx.dispose();\n> 172 |     expect(src).toContain('function openProformaOptions(');\n      |                 ^\n  173 |     expect(src).toContain('function generateProforma(');\n  174 |     expect(src).toContain('function buildProformaHtml(');\n  175 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 172
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"function openProformaOptions(\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  170 |     const src = await (await ctx.get(BASE_URL)).text();\n  171 |     await ctx.dispose();\n> 172 |     expect(src).toContain('function openProformaOptions(');\n      |                 ^\n  173 |     expect(src).toContain('function generateProforma(');\n  174 |     expect(src).toContain('function buildProformaHtml(');\n  175 |   });\n    at /var/www/360lm/tests/sales.spec.js:172:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:50:59.854Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-9a7d3-penProformaOptions-function-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 172
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-75365e3b544e65a1e839",
              "file": "sales.spec.js",
              "line": 168,
              "column": 3
            },
            {
              "title": "source does not contain \"coming soon\" placeholder for Proforma",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1762,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 59,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:51:00.876Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "569c8645201f3ed4d37d-15acc4abb7bcafc83055",
              "file": "sales.spec.js",
              "line": 177,
              "column": 3
            },
            {
              "title": "modal-proforma-opts exists in DOM",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1762,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 40,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:188:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 188
                        },
                        "snippet": "  186 |     const src = await (await ctx.get(BASE_URL)).text();\n  187 |     await ctx.dispose();\n> 188 |     expect(src).toContain('id=\"modal-proforma-opts\"');\n      |                 ^\n  189 |     expect(src).toContain('id=\"proforma-advance-pct\"');\n  190 |   });\n  191 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 188
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  186 |     const src = await (await ctx.get(BASE_URL)).text();\n  187 |     await ctx.dispose();\n> 188 |     expect(src).toContain('id=\"modal-proforma-opts\"');\n      |                 ^\n  189 |     expect(src).toContain('id=\"proforma-advance-pct\"');\n  190 |   });\n  191 |\n    at /var/www/360lm/tests/sales.spec.js:188:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:51:00.983Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-0c974-proforma-opts-exists-in-DOM-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 188
                      }
                    },
                    {
                      "workerIndex": 1763,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 122,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:188:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 188
                        },
                        "snippet": "  186 |     const src = await (await ctx.get(BASE_URL)).text();\n  187 |     await ctx.dispose();\n> 188 |     expect(src).toContain('id=\"modal-proforma-opts\"');\n      |                 ^\n  189 |     expect(src).toContain('id=\"proforma-advance-pct\"');\n  190 |   });\n  191 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 188
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"modal-proforma-opts\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  186 |     const src = await (await ctx.get(BASE_URL)).text();\n  187 |     await ctx.dispose();\n> 188 |     expect(src).toContain('id=\"modal-proforma-opts\"');\n      |                 ^\n  189 |     expect(src).toContain('id=\"proforma-advance-pct\"');\n  190 |   });\n  191 |\n    at /var/www/360lm/tests/sales.spec.js:188:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:51:02.650Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-0c974-proforma-opts-exists-in-DOM-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 188
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-d15cc02e92001363cdf5",
              "file": "sales.spec.js",
              "line": 184,
              "column": 3
            },
            {
              "title": "clients list renders Proforma Invoice button",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1764,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 78,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:196:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 196
                        },
                        "snippet": "  194 |     const src = await (await ctx.get(BASE_URL)).text();\n  195 |     await ctx.dispose();\n> 196 |     expect(src).toContain(\"openProformaOptions('${o.offer_id}')\");\n      |                 ^\n  197 |     expect(src).toContain('Proforma Invoice');\n  198 |   });\n  199 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 196
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  194 |     const src = await (await ctx.get(BASE_URL)).text();\n  195 |     await ctx.dispose();\n> 196 |     expect(src).toContain(\"openProformaOptions('${o.offer_id}')\");\n      |                 ^\n  197 |     expect(src).toContain('Proforma Invoice');\n  198 |   });\n  199 | });\n    at /var/www/360lm/tests/sales.spec.js:196:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:51:04.027Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-a7d72-ers-Proforma-Invoice-button-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 196
                      }
                    },
                    {
                      "workerIndex": 1765,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 69,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/sales.spec.js:196:17",
                        "location": {
                          "file": "/var/www/360lm/tests/sales.spec.js",
                          "column": 17,
                          "line": 196
                        },
                        "snippet": "  194 |     const src = await (await ctx.get(BASE_URL)).text();\n  195 |     await ctx.dispose();\n> 196 |     expect(src).toContain(\"openProformaOptions('${o.offer_id}')\");\n      |                 ^\n  197 |     expect(src).toContain('Proforma Invoice');\n  198 |   });\n  199 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/sales.spec.js",
                            "column": 17,
                            "line": 196
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"openProformaOptions('${o.offer_id}')\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  194 |     const src = await (await ctx.get(BASE_URL)).text();\n  195 |     await ctx.dispose();\n> 196 |     expect(src).toContain(\"openProformaOptions('${o.offer_id}')\");\n      |                 ^\n  197 |     expect(src).toContain('Proforma Invoice');\n  198 |   });\n  199 | });\n    at /var/www/360lm/tests/sales.spec.js:196:17"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:51:05.151Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/sales-Sales-—-Proforma-Inv-a7d72-ers-Proforma-Invoice-button-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/sales.spec.js",
                        "column": 17,
                        "line": 196
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "569c8645201f3ed4d37d-5024f67aa77ec722d8b7",
              "file": "sales.spec.js",
              "line": 192,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "smoke.spec.js",
      "file": "smoke.spec.js",
      "column": 0,
      "line": 0,
      "specs": [
        {
          "title": "hub PWA loads and shows login",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 758,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 5250,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n    at /var/www/360lm/tests/smoke.spec.js:11:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 11
                    },
                    "snippet": "   9 | test('hub PWA loads and shows login', async ({ page }) => {\n  10 |   await page.goto(`${BASE}/hub/`);\n> 11 |   await expect(page).toHaveTitle(/Hub|360/i);\n     |                      ^\n  12 |   // PIN pad or login screen must be present\n  13 |   await expect(page.locator('body')).not.toBeEmpty();\n  14 | });"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 11
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n\n   9 | test('hub PWA loads and shows login', async ({ page }) => {\n  10 |   await page.goto(`${BASE}/hub/`);\n> 11 |   await expect(page).toHaveTitle(/Hub|360/i);\n     |                      ^\n  12 |   // PIN pad or login screen must be present\n  13 |   await expect(page.locator('body')).not.toBeEmpty();\n  14 | });\n    at /var/www/360lm/tests/smoke.spec.js:11:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:03.416Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-hub-PWA-loads-and-shows-login-android-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-hub-PWA-loads-and-shows-login-android-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 11
                  }
                },
                {
                  "workerIndex": 759,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 5238,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n    at /var/www/360lm/tests/smoke.spec.js:11:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 11
                    },
                    "snippet": "   9 | test('hub PWA loads and shows login', async ({ page }) => {\n  10 |   await page.goto(`${BASE}/hub/`);\n> 11 |   await expect(page).toHaveTitle(/Hub|360/i);\n     |                      ^\n  12 |   // PIN pad or login screen must be present\n  13 |   await expect(page.locator('body')).not.toBeEmpty();\n  14 | });"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 11
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n\n   9 | test('hub PWA loads and shows login', async ({ page }) => {\n  10 |   await page.goto(`${BASE}/hub/`);\n> 11 |   await expect(page).toHaveTitle(/Hub|360/i);\n     |                      ^\n  12 |   // PIN pad or login screen must be present\n  13 |   await expect(page.locator('body')).not.toBeEmpty();\n  14 | });\n    at /var/www/360lm/tests/smoke.spec.js:11:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T20:51:09.662Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-hub-PWA-loads-and-shows-login-android-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-hub-PWA-loads-and-shows-login-android-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 11
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "2c14808d47b386fba9eb-cebaca70e2354c92e63b",
          "file": "smoke.spec.js",
          "line": 9,
          "column": 1
        },
        {
          "title": "mobile viewport renders hub correctly",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 760,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 251,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:20:17",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 17,
                      "line": 20
                    },
                    "snippet": "  18 |   await page.goto(`${BASE}/hub/`);\n  19 |   const title = await page.title();\n> 20 |   expect(title).toBeTruthy();\n     |                 ^\n  21 | });\n  22 |\n  23 | test('admin PWA loads', async ({ page }) => {"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 17,
                        "line": 20
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m\n\n  18 |   await page.goto(`${BASE}/hub/`);\n  19 |   const title = await page.title();\n> 20 |   expect(title).toBeTruthy();\n     |                 ^\n  21 | });\n  22 |\n  23 | test('admin PWA loads', async ({ page }) => {\n    at /var/www/360lm/tests/smoke.spec.js:20:17"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:15.837Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-mobile-viewport-renders-hub-correctly-android-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-mobile-viewport-renders-hub-correctly-android-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 17,
                    "line": 20
                  }
                },
                {
                  "workerIndex": 761,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 251,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:20:17",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 17,
                      "line": 20
                    },
                    "snippet": "  18 |   await page.goto(`${BASE}/hub/`);\n  19 |   const title = await page.title();\n> 20 |   expect(title).toBeTruthy();\n     |                 ^\n  21 | });\n  22 |\n  23 | test('admin PWA loads', async ({ page }) => {"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 17,
                        "line": 20
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m\n\n  18 |   await page.goto(`${BASE}/hub/`);\n  19 |   const title = await page.title();\n> 20 |   expect(title).toBeTruthy();\n     |                 ^\n  21 | });\n  22 |\n  23 | test('admin PWA loads', async ({ page }) => {\n    at /var/www/360lm/tests/smoke.spec.js:20:17"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T20:51:17.040Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-mobile-viewport-renders-hub-correctly-android-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-mobile-viewport-renders-hub-correctly-android-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 17,
                    "line": 20
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "2c14808d47b386fba9eb-732475f7bb7577a3f878",
          "file": "smoke.spec.js",
          "line": 16,
          "column": 1
        },
        {
          "title": "admin PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 762,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 297,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:18.291Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-95c9ce02759faa2a8cb2",
          "file": "smoke.spec.js",
          "line": 23,
          "column": 1
        },
        {
          "title": "expense PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 762,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 226,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:18.767Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-f26c63b4d9d6414caed8",
          "file": "smoke.spec.js",
          "line": 28,
          "column": 1
        },
        {
          "title": "sales PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 762,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 187,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:19.015Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-dbc6324e82751d972d77",
          "file": "smoke.spec.js",
          "line": 33,
          "column": 1
        },
        {
          "title": "hr PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 762,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 187,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:19.221Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-c6185fe0c1df8b507b6a",
          "file": "smoke.spec.js",
          "line": 38,
          "column": 1
        },
        {
          "title": "counters PWA loads",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 762,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 178,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:45:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 45
                    },
                    "snippet": "  43 | test('counters PWA loads', async ({ page }) => {\n  44 |   const resp = await page.goto(`${BASE}/counters/`);\n> 45 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  46 |   await expect(page.locator('body')).not.toBeEmpty();\n  47 | });\n  48 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 45
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  43 | test('counters PWA loads', async ({ page }) => {\n  44 |   const resp = await page.goto(`${BASE}/counters/`);\n> 45 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  46 |   await expect(page.locator('body')).not.toBeEmpty();\n  47 | });\n  48 |\n    at /var/www/360lm/tests/smoke.spec.js:45:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:19.434Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-counters-PWA-loads-android-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-counters-PWA-loads-android-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 45
                  }
                },
                {
                  "workerIndex": 763,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 270,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:45:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 45
                    },
                    "snippet": "  43 | test('counters PWA loads', async ({ page }) => {\n  44 |   const resp = await page.goto(`${BASE}/counters/`);\n> 45 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  46 |   await expect(page.locator('body')).not.toBeEmpty();\n  47 | });\n  48 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 45
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  43 | test('counters PWA loads', async ({ page }) => {\n  44 |   const resp = await page.goto(`${BASE}/counters/`);\n> 45 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  46 |   await expect(page.locator('body')).not.toBeEmpty();\n  47 | });\n  48 |\n    at /var/www/360lm/tests/smoke.spec.js:45:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T20:51:20.471Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-counters-PWA-loads-android-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-counters-PWA-loads-android-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 45
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "2c14808d47b386fba9eb-b85d172d779790de33d3",
          "file": "smoke.spec.js",
          "line": 43,
          "column": 1
        },
        {
          "title": "recce PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 764,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 271,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:21.703Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-b46f9f513b9d626cd20f",
          "file": "smoke.spec.js",
          "line": 49,
          "column": 1
        },
        {
          "title": "vehicle PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 764,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 220,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:22.130Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-a3acae60c9add82811d9",
          "file": "smoke.spec.js",
          "line": 54,
          "column": 1
        },
        {
          "title": "finance custodian PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 764,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 215,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:22.379Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-b920bad892cbc37b43b1",
          "file": "smoke.spec.js",
          "line": 59,
          "column": 1
        },
        {
          "title": "printing PWA loads",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 764,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 198,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:66:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 66
                    },
                    "snippet": "  64 | test('printing PWA loads', async ({ page }) => {\n  65 |   const resp = await page.goto(`${BASE}/printing/`);\n> 66 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  67 |   await expect(page.locator('body')).not.toBeEmpty();\n  68 | });\n  69 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 66
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  64 | test('printing PWA loads', async ({ page }) => {\n  65 |   const resp = await page.goto(`${BASE}/printing/`);\n> 66 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  67 |   await expect(page.locator('body')).not.toBeEmpty();\n  68 | });\n  69 |\n    at /var/www/360lm/tests/smoke.spec.js:66:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:22.614Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-printing-PWA-loads-android-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-printing-PWA-loads-android-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 66
                  }
                },
                {
                  "workerIndex": 765,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 340,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:66:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 66
                    },
                    "snippet": "  64 | test('printing PWA loads', async ({ page }) => {\n  65 |   const resp = await page.goto(`${BASE}/printing/`);\n> 66 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  67 |   await expect(page.locator('body')).not.toBeEmpty();\n  68 | });\n  69 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 66
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  64 | test('printing PWA loads', async ({ page }) => {\n  65 |   const resp = await page.goto(`${BASE}/printing/`);\n> 66 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  67 |   await expect(page.locator('body')).not.toBeEmpty();\n  68 | });\n  69 |\n    at /var/www/360lm/tests/smoke.spec.js:66:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T20:51:23.638Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-printing-PWA-loads-android-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-printing-PWA-loads-android-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 66
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "2c14808d47b386fba9eb-095cd29e86f52cfd535d",
          "file": "smoke.spec.js",
          "line": 64,
          "column": 1
        },
        {
          "title": "installation PWA loads",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 766,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 284,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:72:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 72
                    },
                    "snippet": "  70 | test('installation PWA loads', async ({ page }) => {\n  71 |   const resp = await page.goto(`${BASE}/installation/current/`);\n> 72 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  73 |   await expect(page.locator('body')).not.toBeEmpty();\n  74 | });\n  75 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 72
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  70 | test('installation PWA loads', async ({ page }) => {\n  71 |   const resp = await page.goto(`${BASE}/installation/current/`);\n> 72 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  73 |   await expect(page.locator('body')).not.toBeEmpty();\n  74 | });\n  75 |\n    at /var/www/360lm/tests/smoke.spec.js:72:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:25.008Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-installation-PWA-loads-android-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-installation-PWA-loads-android-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 72
                  }
                },
                {
                  "workerIndex": 767,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 325,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:72:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 72
                    },
                    "snippet": "  70 | test('installation PWA loads', async ({ page }) => {\n  71 |   const resp = await page.goto(`${BASE}/installation/current/`);\n> 72 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  73 |   await expect(page.locator('body')).not.toBeEmpty();\n  74 | });\n  75 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 72
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  70 | test('installation PWA loads', async ({ page }) => {\n  71 |   const resp = await page.goto(`${BASE}/installation/current/`);\n> 72 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  73 |   await expect(page.locator('body')).not.toBeEmpty();\n  74 | });\n  75 |\n    at /var/www/360lm/tests/smoke.spec.js:72:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T20:51:26.234Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-installation-PWA-loads-android-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-installation-PWA-loads-android-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 72
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "2c14808d47b386fba9eb-613814a3724a77175741",
          "file": "smoke.spec.js",
          "line": 70,
          "column": 1
        },
        {
          "title": "vrs PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 768,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 314,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:27.636Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-d08c5d40b1d42a0337f4",
          "file": "smoke.spec.js",
          "line": 76,
          "column": 1
        },
        {
          "title": "production PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 768,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 236,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:28.115Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-271bd146c136506fc01f",
          "file": "smoke.spec.js",
          "line": 81,
          "column": 1
        },
        {
          "title": "learn PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 768,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 193,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:28.378Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-c63415f57866c5e99aff",
          "file": "smoke.spec.js",
          "line": 86,
          "column": 1
        },
        {
          "title": "contacts PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 768,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 218,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:28.594Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-0a34cad16668cc5105ac",
          "file": "smoke.spec.js",
          "line": 91,
          "column": 1
        },
        {
          "title": "dispatch PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "android-chrome",
              "projectName": "android-chrome",
              "results": [
                {
                  "workerIndex": 768,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 439,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T20:51:28.838Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-a0ebdd55e292d3963519",
          "file": "smoke.spec.js",
          "line": 96,
          "column": 1
        },
        {
          "title": "hub PWA loads and shows login",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1766,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 5215,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n    at /var/www/360lm/tests/smoke.spec.js:11:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 11
                    },
                    "snippet": "   9 | test('hub PWA loads and shows login', async ({ page }) => {\n  10 |   await page.goto(`${BASE}/hub/`);\n> 11 |   await expect(page).toHaveTitle(/Hub|360/i);\n     |                      ^\n  12 |   // PIN pad or login screen must be present\n  13 |   await expect(page.locator('body')).not.toBeEmpty();\n  14 | });"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 11
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n\n   9 | test('hub PWA loads and shows login', async ({ page }) => {\n  10 |   await page.goto(`${BASE}/hub/`);\n> 11 |   await expect(page).toHaveTitle(/Hub|360/i);\n     |                      ^\n  12 |   // PIN pad or login screen must be present\n  13 |   await expect(page.locator('body')).not.toBeEmpty();\n  14 | });\n    at /var/www/360lm/tests/smoke.spec.js:11:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:06.217Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-hub-PWA-loads-and-shows-login-desktop-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-hub-PWA-loads-and-shows-login-desktop-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 11
                  }
                },
                {
                  "workerIndex": 1767,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 5227,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n    at /var/www/360lm/tests/smoke.spec.js:11:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 11
                    },
                    "snippet": "   9 | test('hub PWA loads and shows login', async ({ page }) => {\n  10 |   await page.goto(`${BASE}/hub/`);\n> 11 |   await expect(page).toHaveTitle(/Hub|360/i);\n     |                      ^\n  12 |   // PIN pad or login screen must be present\n  13 |   await expect(page.locator('body')).not.toBeEmpty();\n  14 | });"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 11
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Hub|360/i\u001b[39m\nReceived string:  \u001b[31m\"\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    14 × unexpected value \"\"\u001b[22m\n\n\n   9 | test('hub PWA loads and shows login', async ({ page }) => {\n  10 |   await page.goto(`${BASE}/hub/`);\n> 11 |   await expect(page).toHaveTitle(/Hub|360/i);\n     |                      ^\n  12 |   // PIN pad or login screen must be present\n  13 |   await expect(page.locator('body')).not.toBeEmpty();\n  14 | });\n    at /var/www/360lm/tests/smoke.spec.js:11:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T23:51:12.456Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-hub-PWA-loads-and-shows-login-desktop-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-hub-PWA-loads-and-shows-login-desktop-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 11
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "2c14808d47b386fba9eb-550064d88571af206451",
          "file": "smoke.spec.js",
          "line": 9,
          "column": 1
        },
        {
          "title": "mobile viewport renders hub correctly",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1768,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 270,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:20:17",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 17,
                      "line": 20
                    },
                    "snippet": "  18 |   await page.goto(`${BASE}/hub/`);\n  19 |   const title = await page.title();\n> 20 |   expect(title).toBeTruthy();\n     |                 ^\n  21 | });\n  22 |\n  23 | test('admin PWA loads', async ({ page }) => {"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 17,
                        "line": 20
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m\n\n  18 |   await page.goto(`${BASE}/hub/`);\n  19 |   const title = await page.title();\n> 20 |   expect(title).toBeTruthy();\n     |                 ^\n  21 | });\n  22 |\n  23 | test('admin PWA loads', async ({ page }) => {\n    at /var/www/360lm/tests/smoke.spec.js:20:17"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:18.635Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-mobile-viewport-renders-hub-correctly-desktop-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-mobile-viewport-renders-hub-correctly-desktop-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 17,
                    "line": 20
                  }
                },
                {
                  "workerIndex": 1769,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 288,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:20:17",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 17,
                      "line": 20
                    },
                    "snippet": "  18 |   await page.goto(`${BASE}/hub/`);\n  19 |   const title = await page.title();\n> 20 |   expect(title).toBeTruthy();\n     |                 ^\n  21 | });\n  22 |\n  23 | test('admin PWA loads', async ({ page }) => {"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 17,
                        "line": 20
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBeTruthy\u001b[2m()\u001b[22m\n\nReceived: \u001b[31m\"\"\u001b[39m\n\n  18 |   await page.goto(`${BASE}/hub/`);\n  19 |   const title = await page.title();\n> 20 |   expect(title).toBeTruthy();\n     |                 ^\n  21 | });\n  22 |\n  23 | test('admin PWA loads', async ({ page }) => {\n    at /var/www/360lm/tests/smoke.spec.js:20:17"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T23:51:19.835Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-mobile-viewport-renders-hub-correctly-desktop-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-mobile-viewport-renders-hub-correctly-desktop-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 17,
                    "line": 20
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "2c14808d47b386fba9eb-02a60e0b6e83112d55bc",
          "file": "smoke.spec.js",
          "line": 16,
          "column": 1
        },
        {
          "title": "admin PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1770,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 264,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:21.040Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-09d46dbbf184fc009a35",
          "file": "smoke.spec.js",
          "line": 23,
          "column": 1
        },
        {
          "title": "expense PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1770,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 196,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:21.470Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-b2570533a7b427b73ec4",
          "file": "smoke.spec.js",
          "line": 28,
          "column": 1
        },
        {
          "title": "sales PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1770,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 189,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:21.686Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-b0968d9f1f7ddb6770a6",
          "file": "smoke.spec.js",
          "line": 33,
          "column": 1
        },
        {
          "title": "hr PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1770,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 197,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:21.893Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-642986ab661f409cf90b",
          "file": "smoke.spec.js",
          "line": 38,
          "column": 1
        },
        {
          "title": "counters PWA loads",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1770,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 198,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:45:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 45
                    },
                    "snippet": "  43 | test('counters PWA loads', async ({ page }) => {\n  44 |   const resp = await page.goto(`${BASE}/counters/`);\n> 45 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  46 |   await expect(page.locator('body')).not.toBeEmpty();\n  47 | });\n  48 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 45
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  43 | test('counters PWA loads', async ({ page }) => {\n  44 |   const resp = await page.goto(`${BASE}/counters/`);\n> 45 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  46 |   await expect(page.locator('body')).not.toBeEmpty();\n  47 | });\n  48 |\n    at /var/www/360lm/tests/smoke.spec.js:45:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:22.114Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-counters-PWA-loads-desktop-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-counters-PWA-loads-desktop-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 45
                  }
                },
                {
                  "workerIndex": 1771,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 269,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:45:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 45
                    },
                    "snippet": "  43 | test('counters PWA loads', async ({ page }) => {\n  44 |   const resp = await page.goto(`${BASE}/counters/`);\n> 45 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  46 |   await expect(page.locator('body')).not.toBeEmpty();\n  47 | });\n  48 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 45
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  43 | test('counters PWA loads', async ({ page }) => {\n  44 |   const resp = await page.goto(`${BASE}/counters/`);\n> 45 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  46 |   await expect(page.locator('body')).not.toBeEmpty();\n  47 | });\n  48 |\n    at /var/www/360lm/tests/smoke.spec.js:45:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T23:51:23.133Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-counters-PWA-loads-desktop-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-counters-PWA-loads-desktop-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 45
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "2c14808d47b386fba9eb-2a92803c83ee7575752f",
          "file": "smoke.spec.js",
          "line": 43,
          "column": 1
        },
        {
          "title": "recce PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1772,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 278,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:24.395Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-78780e7e39982b9e12e4",
          "file": "smoke.spec.js",
          "line": 49,
          "column": 1
        },
        {
          "title": "vehicle PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1772,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 198,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:24.832Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-9185b9071d648629bc81",
          "file": "smoke.spec.js",
          "line": 54,
          "column": 1
        },
        {
          "title": "finance custodian PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1772,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 195,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:25.061Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-e2fa7184561b80b0d666",
          "file": "smoke.spec.js",
          "line": 59,
          "column": 1
        },
        {
          "title": "printing PWA loads",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1772,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 180,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:66:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 66
                    },
                    "snippet": "  64 | test('printing PWA loads', async ({ page }) => {\n  65 |   const resp = await page.goto(`${BASE}/printing/`);\n> 66 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  67 |   await expect(page.locator('body')).not.toBeEmpty();\n  68 | });\n  69 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 66
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  64 | test('printing PWA loads', async ({ page }) => {\n  65 |   const resp = await page.goto(`${BASE}/printing/`);\n> 66 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  67 |   await expect(page.locator('body')).not.toBeEmpty();\n  68 | });\n  69 |\n    at /var/www/360lm/tests/smoke.spec.js:66:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:25.281Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-printing-PWA-loads-desktop-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-printing-PWA-loads-desktop-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 66
                  }
                },
                {
                  "workerIndex": 1773,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 259,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:66:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 66
                    },
                    "snippet": "  64 | test('printing PWA loads', async ({ page }) => {\n  65 |   const resp = await page.goto(`${BASE}/printing/`);\n> 66 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  67 |   await expect(page.locator('body')).not.toBeEmpty();\n  68 | });\n  69 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 66
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  64 | test('printing PWA loads', async ({ page }) => {\n  65 |   const resp = await page.goto(`${BASE}/printing/`);\n> 66 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  67 |   await expect(page.locator('body')).not.toBeEmpty();\n  68 | });\n  69 |\n    at /var/www/360lm/tests/smoke.spec.js:66:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T23:51:26.307Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-printing-PWA-loads-desktop-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-printing-PWA-loads-desktop-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 66
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "2c14808d47b386fba9eb-ecccc88ff4489d21a54e",
          "file": "smoke.spec.js",
          "line": 64,
          "column": 1
        },
        {
          "title": "installation PWA loads",
          "ok": false,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1774,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 311,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:72:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 72
                    },
                    "snippet": "  70 | test('installation PWA loads', async ({ page }) => {\n  71 |   const resp = await page.goto(`${BASE}/installation/current/`);\n> 72 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  73 |   await expect(page.locator('body')).not.toBeEmpty();\n  74 | });\n  75 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 72
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  70 | test('installation PWA loads', async ({ page }) => {\n  71 |   const resp = await page.goto(`${BASE}/installation/current/`);\n> 72 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  73 |   await expect(page.locator('body')).not.toBeEmpty();\n  74 | });\n  75 |\n    at /var/www/360lm/tests/smoke.spec.js:72:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:27.504Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-installation-PWA-loads-desktop-chrome/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-installation-PWA-loads-desktop-chrome/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 72
                  }
                },
                {
                  "workerIndex": 1775,
                  "parallelIndex": 0,
                  "status": "failed",
                  "duration": 268,
                  "error": {
                    "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m",
                    "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n    at /var/www/360lm/tests/smoke.spec.js:72:22",
                    "location": {
                      "file": "/var/www/360lm/tests/smoke.spec.js",
                      "column": 22,
                      "line": 72
                    },
                    "snippet": "  70 | test('installation PWA loads', async ({ page }) => {\n  71 |   const resp = await page.goto(`${BASE}/installation/current/`);\n> 72 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  73 |   await expect(page.locator('body')).not.toBeEmpty();\n  74 | });\n  75 |"
                  },
                  "errors": [
                    {
                      "location": {
                        "file": "/var/www/360lm/tests/smoke.spec.js",
                        "column": 22,
                        "line": 72
                      },
                      "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected value: \u001b[32m404\u001b[39m\nReceived array: \u001b[31m[200, 302]\u001b[39m\n\n  70 | test('installation PWA loads', async ({ page }) => {\n  71 |   const resp = await page.goto(`${BASE}/installation/current/`);\n> 72 |   expect([200, 302]).toContain(resp?.status());\n     |                      ^\n  73 |   await expect(page.locator('body')).not.toBeEmpty();\n  74 | });\n  75 |\n    at /var/www/360lm/tests/smoke.spec.js:72:22"
                    }
                  ],
                  "stdout": [],
                  "stderr": [],
                  "retry": 1,
                  "startTime": "2026-09-15T23:51:28.787Z",
                  "annotations": [],
                  "attachments": [
                    {
                      "name": "screenshot",
                      "contentType": "image/png",
                      "path": "/var/www/360lm/test-results/smoke-installation-PWA-loads-desktop-chrome-retry1/test-failed-1.png"
                    },
                    {
                      "name": "error-context",
                      "contentType": "text/markdown",
                      "path": "/var/www/360lm/test-results/smoke-installation-PWA-loads-desktop-chrome-retry1/error-context.md"
                    }
                  ],
                  "errorLocation": {
                    "file": "/var/www/360lm/tests/smoke.spec.js",
                    "column": 22,
                    "line": 72
                  }
                }
              ],
              "status": "unexpected"
            }
          ],
          "id": "2c14808d47b386fba9eb-50ec5a88d02a42a9ec06",
          "file": "smoke.spec.js",
          "line": 70,
          "column": 1
        },
        {
          "title": "vrs PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1776,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 280,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:30.048Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-3a478a46c02b18bff397",
          "file": "smoke.spec.js",
          "line": 76,
          "column": 1
        },
        {
          "title": "production PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1776,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 195,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:30.480Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-24e0c792acebba3a036d",
          "file": "smoke.spec.js",
          "line": 81,
          "column": 1
        },
        {
          "title": "learn PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1776,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 199,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:30.695Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-647882ddd302825210b7",
          "file": "smoke.spec.js",
          "line": 86,
          "column": 1
        },
        {
          "title": "contacts PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1776,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 204,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:30.912Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-c9fc3654da081896cab6",
          "file": "smoke.spec.js",
          "line": 91,
          "column": 1
        },
        {
          "title": "dispatch PWA loads",
          "ok": true,
          "tags": [],
          "tests": [
            {
              "timeout": 30000,
              "annotations": [],
              "expectedStatus": "passed",
              "projectId": "desktop-chrome",
              "projectName": "desktop-chrome",
              "results": [
                {
                  "workerIndex": 1776,
                  "parallelIndex": 0,
                  "status": "passed",
                  "duration": 450,
                  "errors": [],
                  "stdout": [],
                  "stderr": [],
                  "retry": 0,
                  "startTime": "2026-09-15T23:51:31.138Z",
                  "annotations": [],
                  "attachments": []
                }
              ],
              "status": "expected"
            }
          ],
          "id": "2c14808d47b386fba9eb-dc251226b7e839fffc92",
          "file": "smoke.spec.js",
          "line": 96,
          "column": 1
        }
      ]
    },
    {
      "title": "stores.spec.js",
      "file": "stores.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Stores — Page Load",
          "file": "stores.spec.js",
          "line": 18,
          "column": 6,
          "specs": [
            {
              "title": "no session → login screen visible (not redirect to hub)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 768,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8141,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:21:49",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 49,
                          "line": 21
                        },
                        "snippet": "  19 |   test('no session → login screen visible (not redirect to hub)', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 8000 });\n     |                                                 ^\n  22 |     await expect(page).toHaveURL(BASE_URL);\n  23 |   });\n  24 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 49,
                            "line": 21
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n\n\n  19 |   test('no session → login screen visible (not redirect to hub)', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 8000 });\n     |                                                 ^\n  22 |     await expect(page).toHaveURL(BASE_URL);\n  23 |   });\n  24 |\n    at /var/www/360lm/tests/stores.spec.js:21:49"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:51:29.319Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--6da39-isible-not-redirect-to-hub--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--6da39-isible-not-redirect-to-hub--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 49,
                        "line": 21
                      }
                    },
                    {
                      "workerIndex": 769,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8233,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:21:49",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 49,
                          "line": 21
                        },
                        "snippet": "  19 |   test('no session → login screen visible (not redirect to hub)', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 8000 });\n     |                                                 ^\n  22 |     await expect(page).toHaveURL(BASE_URL);\n  23 |   });\n  24 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 49,
                            "line": 21
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n\n\n  19 |   test('no session → login screen visible (not redirect to hub)', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 8000 });\n     |                                                 ^\n  22 |     await expect(page).toHaveURL(BASE_URL);\n  23 |   });\n  24 |\n    at /var/www/360lm/tests/stores.spec.js:21:49"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:51:38.248Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--6da39-isible-not-redirect-to-hub--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--6da39-isible-not-redirect-to-hub--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 49,
                        "line": 21
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-6be34f29784a2ba69480",
              "file": "stores.spec.js",
              "line": 19,
              "column": 3
            },
            {
              "title": "with stored user → home screen visible",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 770,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8207,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:28:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 28
                        },
                        "snippet": "  26 |     await injectStoresUser(page);\n  27 |     await page.goto(BASE_URL);\n> 28 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  29 |   });\n  30 | });\n  31 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 28
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  26 |     await injectStoresUser(page);\n  27 |     await page.goto(BASE_URL);\n> 28 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  29 |   });\n  30 | });\n  31 |\n    at /var/www/360lm/tests/stores.spec.js:28:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:51:47.555Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--283c0--user-→-home-screen-visible-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--283c0--user-→-home-screen-visible-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 28
                      }
                    },
                    {
                      "workerIndex": 771,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8250,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:28:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 28
                        },
                        "snippet": "  26 |     await injectStoresUser(page);\n  27 |     await page.goto(BASE_URL);\n> 28 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  29 |   });\n  30 | });\n  31 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 28
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  26 |     await injectStoresUser(page);\n  27 |     await page.goto(BASE_URL);\n> 28 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  29 |   });\n  30 | });\n  31 |\n    at /var/www/360lm/tests/stores.spec.js:28:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:51:56.722Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--283c0--user-→-home-screen-visible-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--283c0--user-→-home-screen-visible-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 28
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-315ee5aaab8ed2d0174f",
              "file": "stores.spec.js",
              "line": 25,
              "column": 3
            },
            {
              "title": "no session → login screen visible (not redirect to hub)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1776,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8148,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:21:49",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 49,
                          "line": 21
                        },
                        "snippet": "  19 |   test('no session → login screen visible (not redirect to hub)', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 8000 });\n     |                                                 ^\n  22 |     await expect(page).toHaveURL(BASE_URL);\n  23 |   });\n  24 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 49,
                            "line": 21
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n\n\n  19 |   test('no session → login screen visible (not redirect to hub)', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 8000 });\n     |                                                 ^\n  22 |     await expect(page).toHaveURL(BASE_URL);\n  23 |   });\n  24 |\n    at /var/www/360lm/tests/stores.spec.js:21:49"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:51:31.613Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--6da39-isible-not-redirect-to-hub--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--6da39-isible-not-redirect-to-hub--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 49,
                        "line": 21
                      }
                    },
                    {
                      "workerIndex": 1777,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8226,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:21:49",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 49,
                          "line": 21
                        },
                        "snippet": "  19 |   test('no session → login screen visible (not redirect to hub)', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 8000 });\n     |                                                 ^\n  22 |     await expect(page).toHaveURL(BASE_URL);\n  23 |   });\n  24 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 49,
                            "line": 21
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-login')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-login')\u001b[22m\n\n\n  19 |   test('no session → login screen visible (not redirect to hub)', async ({ page }) => {\n  20 |     await page.goto(BASE_URL);\n> 21 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 8000 });\n     |                                                 ^\n  22 |     await expect(page).toHaveURL(BASE_URL);\n  23 |   });\n  24 |\n    at /var/www/360lm/tests/stores.spec.js:21:49"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:51:40.560Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--6da39-isible-not-redirect-to-hub--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--6da39-isible-not-redirect-to-hub--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 49,
                        "line": 21
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-6f744547ba3c1f9c59d2",
              "file": "stores.spec.js",
              "line": 19,
              "column": 3
            },
            {
              "title": "with stored user → home screen visible",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1778,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8280,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:28:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 28
                        },
                        "snippet": "  26 |     await injectStoresUser(page);\n  27 |     await page.goto(BASE_URL);\n> 28 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  29 |   });\n  30 | });\n  31 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 28
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  26 |     await injectStoresUser(page);\n  27 |     await page.goto(BASE_URL);\n> 28 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  29 |   });\n  30 | });\n  31 |\n    at /var/www/360lm/tests/stores.spec.js:28:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:51:49.748Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--283c0--user-→-home-screen-visible-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--283c0--user-→-home-screen-visible-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 28
                      }
                    },
                    {
                      "workerIndex": 1779,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8262,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:28:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 28
                        },
                        "snippet": "  26 |     await injectStoresUser(page);\n  27 |     await page.goto(BASE_URL);\n> 28 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  29 |   });\n  30 | });\n  31 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 28
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  26 |     await injectStoresUser(page);\n  27 |     await page.goto(BASE_URL);\n> 28 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  29 |   });\n  30 | });\n  31 |\n    at /var/www/360lm/tests/stores.spec.js:28:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:51:59.046Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--283c0--user-→-home-screen-visible-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Page-Load--283c0--user-→-home-screen-visible-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 28
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-d9506cf1142a68c31bb9",
              "file": "stores.spec.js",
              "line": 25,
              "column": 3
            }
          ]
        },
        {
          "title": "Stores — User Bar: Home Screen",
          "file": "stores.spec.js",
          "line": 32,
          "column": 6,
          "specs": [
            {
              "title": "home-user-sub shows user name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 772,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8254,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:39:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 39
                        },
                        "snippet": "  37 |   test('home-user-sub shows user name', async ({ page }) => {\n  38 |     await page.goto(BASE_URL);\n> 39 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  40 |     await expect(page.locator('#home-user-sub')).toHaveText(/Harish/);\n  41 |   });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  37 |   test('home-user-sub shows user name', async ({ page }) => {\n  38 |     await page.goto(BASE_URL);\n> 39 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  40 |     await expect(page.locator('#home-user-sub')).toHaveText(/Harish/);\n  41 |   });\n  42 |\n    at /var/www/360lm/tests/stores.spec.js:39:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:52:06.160Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-ca37f-me-user-sub-shows-user-name-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-ca37f-me-user-sub-shows-user-name-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 39
                      }
                    },
                    {
                      "workerIndex": 773,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8215,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:39:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 39
                        },
                        "snippet": "  37 |   test('home-user-sub shows user name', async ({ page }) => {\n  38 |     await page.goto(BASE_URL);\n> 39 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  40 |     await expect(page.locator('#home-user-sub')).toHaveText(/Harish/);\n  41 |   });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  37 |   test('home-user-sub shows user name', async ({ page }) => {\n  38 |     await page.goto(BASE_URL);\n> 39 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  40 |     await expect(page.locator('#home-user-sub')).toHaveText(/Harish/);\n  41 |   });\n  42 |\n    at /var/www/360lm/tests/stores.spec.js:39:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:52:15.385Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-ca37f-me-user-sub-shows-user-name-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-ca37f-me-user-sub-shows-user-name-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 39
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-9428ca519c5ead862b26",
              "file": "stores.spec.js",
              "line": 37,
              "column": 3
            },
            {
              "title": "home hdr has Logout button",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 774,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8222,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:45:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 45
                        },
                        "snippet": "  43 |   test('home hdr has Logout button', async ({ page }) => {\n  44 |     await page.goto(BASE_URL);\n> 45 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  46 |     await expect(page.locator('#screen-home .hdr .stores-logout')).toBeVisible();\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  43 |   test('home hdr has Logout button', async ({ page }) => {\n  44 |     await page.goto(BASE_URL);\n> 45 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  46 |     await expect(page.locator('#screen-home .hdr .stores-logout')).toBeVisible();\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/stores.spec.js:45:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:52:24.520Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-17eb2--home-hdr-has-Logout-button-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-17eb2--home-hdr-has-Logout-button-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 45
                      }
                    },
                    {
                      "workerIndex": 775,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8247,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:45:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 45
                        },
                        "snippet": "  43 |   test('home hdr has Logout button', async ({ page }) => {\n  44 |     await page.goto(BASE_URL);\n> 45 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  46 |     await expect(page.locator('#screen-home .hdr .stores-logout')).toBeVisible();\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  43 |   test('home hdr has Logout button', async ({ page }) => {\n  44 |     await page.goto(BASE_URL);\n> 45 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  46 |     await expect(page.locator('#screen-home .hdr .stores-logout')).toBeVisible();\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/stores.spec.js:45:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:52:33.752Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-17eb2--home-hdr-has-Logout-button-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-17eb2--home-hdr-has-Logout-button-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 45
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-4da037e1189ad6270752",
              "file": "stores.spec.js",
              "line": 43,
              "column": 3
            },
            {
              "title": "logout → shows login screen and clears session",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 776,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8235,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:51:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 51
                        },
                        "snippet": "  49 |   test('logout → shows login screen and clears session', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  52 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  53 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 4000 });\n  54 |     // Main nav hidden after logout"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 51
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  49 |   test('logout → shows login screen and clears session', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  52 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  53 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 4000 });\n  54 |     // Main nav hidden after logout\n    at /var/www/360lm/tests/stores.spec.js:51:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:52:42.942Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-dd720-n-screen-and-clears-session-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-dd720-n-screen-and-clears-session-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 51
                      }
                    },
                    {
                      "workerIndex": 777,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8256,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:51:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 51
                        },
                        "snippet": "  49 |   test('logout → shows login screen and clears session', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  52 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  53 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 4000 });\n  54 |     // Main nav hidden after logout"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 51
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  49 |   test('logout → shows login screen and clears session', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  52 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  53 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 4000 });\n  54 |     // Main nav hidden after logout\n    at /var/www/360lm/tests/stores.spec.js:51:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:52:52.157Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-dd720-n-screen-and-clears-session-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-dd720-n-screen-and-clears-session-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 51
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-6897d73836eb264a1da0",
              "file": "stores.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "after logout, stores_user removed from localStorage",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 778,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8357,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:60:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 60
                        },
                        "snippet": "  58 |   test('after logout, stores_user removed from localStorage', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  61 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  62 |     await page.waitForSelector('#screen-login', { timeout: 4000 });\n  63 |     const stored = await page.evaluate(() => localStorage.getItem('stores_user'));"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 60
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  58 |   test('after logout, stores_user removed from localStorage', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  61 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  62 |     await page.waitForSelector('#screen-login', { timeout: 4000 });\n  63 |     const stored = await page.evaluate(() => localStorage.getItem('stores_user'));\n    at /var/www/360lm/tests/stores.spec.js:60:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:53:01.676Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-cce29-r-removed-from-localStorage-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-cce29-r-removed-from-localStorage-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 60
                      }
                    },
                    {
                      "workerIndex": 779,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8217,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:60:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 60
                        },
                        "snippet": "  58 |   test('after logout, stores_user removed from localStorage', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  61 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  62 |     await page.waitForSelector('#screen-login', { timeout: 4000 });\n  63 |     const stored = await page.evaluate(() => localStorage.getItem('stores_user'));"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 60
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  58 |   test('after logout, stores_user removed from localStorage', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  61 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  62 |     await page.waitForSelector('#screen-login', { timeout: 4000 });\n  63 |     const stored = await page.evaluate(() => localStorage.getItem('stores_user'));\n    at /var/www/360lm/tests/stores.spec.js:60:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:53:11.163Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-cce29-r-removed-from-localStorage-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-cce29-r-removed-from-localStorage-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 60
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-fea865c7ee09d2d49edd",
              "file": "stores.spec.js",
              "line": 58,
              "column": 3
            },
            {
              "title": "home-user-sub shows user name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1780,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8234,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:39:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 39
                        },
                        "snippet": "  37 |   test('home-user-sub shows user name', async ({ page }) => {\n  38 |     await page.goto(BASE_URL);\n> 39 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  40 |     await expect(page.locator('#home-user-sub')).toHaveText(/Harish/);\n  41 |   });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  37 |   test('home-user-sub shows user name', async ({ page }) => {\n  38 |     await page.goto(BASE_URL);\n> 39 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  40 |     await expect(page.locator('#home-user-sub')).toHaveText(/Harish/);\n  41 |   });\n  42 |\n    at /var/www/360lm/tests/stores.spec.js:39:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:52:08.298Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-ca37f-me-user-sub-shows-user-name-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-ca37f-me-user-sub-shows-user-name-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 39
                      }
                    },
                    {
                      "workerIndex": 1781,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8238,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:39:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 39
                        },
                        "snippet": "  37 |   test('home-user-sub shows user name', async ({ page }) => {\n  38 |     await page.goto(BASE_URL);\n> 39 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  40 |     await expect(page.locator('#home-user-sub')).toHaveText(/Harish/);\n  41 |   });\n  42 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 39
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  37 |   test('home-user-sub shows user name', async ({ page }) => {\n  38 |     await page.goto(BASE_URL);\n> 39 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  40 |     await expect(page.locator('#home-user-sub')).toHaveText(/Harish/);\n  41 |   });\n  42 |\n    at /var/www/360lm/tests/stores.spec.js:39:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:52:17.522Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-ca37f-me-user-sub-shows-user-name-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-ca37f-me-user-sub-shows-user-name-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 39
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-49d4ad47b556e59e391d",
              "file": "stores.spec.js",
              "line": 37,
              "column": 3
            },
            {
              "title": "home hdr has Logout button",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1782,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8257,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:45:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 45
                        },
                        "snippet": "  43 |   test('home hdr has Logout button', async ({ page }) => {\n  44 |     await page.goto(BASE_URL);\n> 45 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  46 |     await expect(page.locator('#screen-home .hdr .stores-logout')).toBeVisible();\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  43 |   test('home hdr has Logout button', async ({ page }) => {\n  44 |     await page.goto(BASE_URL);\n> 45 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  46 |     await expect(page.locator('#screen-home .hdr .stores-logout')).toBeVisible();\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/stores.spec.js:45:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:52:26.752Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-17eb2--home-hdr-has-Logout-button-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-17eb2--home-hdr-has-Logout-button-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 45
                      }
                    },
                    {
                      "workerIndex": 1783,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8253,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:45:55",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 55,
                          "line": 45
                        },
                        "snippet": "  43 |   test('home hdr has Logout button', async ({ page }) => {\n  44 |     await page.goto(BASE_URL);\n> 45 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  46 |     await expect(page.locator('#screen-home .hdr .stores-logout')).toBeVisible();\n  47 |   });\n  48 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 55,
                            "line": 45
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#screen-home.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#screen-home.active')\u001b[22m\n\n\n  43 |   test('home hdr has Logout button', async ({ page }) => {\n  44 |     await page.goto(BASE_URL);\n> 45 |     await expect(page.locator('#screen-home.active')).toBeVisible({ timeout: 8000 });\n     |                                                       ^\n  46 |     await expect(page.locator('#screen-home .hdr .stores-logout')).toBeVisible();\n  47 |   });\n  48 |\n    at /var/www/360lm/tests/stores.spec.js:45:55"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:52:36.048Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-17eb2--home-hdr-has-Logout-button-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-17eb2--home-hdr-has-Logout-button-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 55,
                        "line": 45
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-67586434387bdee2567d",
              "file": "stores.spec.js",
              "line": 43,
              "column": 3
            },
            {
              "title": "logout → shows login screen and clears session",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1784,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8237,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:51:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 51
                        },
                        "snippet": "  49 |   test('logout → shows login screen and clears session', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  52 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  53 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 4000 });\n  54 |     // Main nav hidden after logout"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 51
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  49 |   test('logout → shows login screen and clears session', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  52 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  53 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 4000 });\n  54 |     // Main nav hidden after logout\n    at /var/www/360lm/tests/stores.spec.js:51:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:52:45.348Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-dd720-n-screen-and-clears-session-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-dd720-n-screen-and-clears-session-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 51
                      }
                    },
                    {
                      "workerIndex": 1785,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8281,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:51:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 51
                        },
                        "snippet": "  49 |   test('logout → shows login screen and clears session', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  52 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  53 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 4000 });\n  54 |     // Main nav hidden after logout"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 51
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  49 |   test('logout → shows login screen and clears session', async ({ page }) => {\n  50 |     await page.goto(BASE_URL);\n> 51 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  52 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  53 |     await expect(page.locator('#screen-login')).toBeVisible({ timeout: 4000 });\n  54 |     // Main nav hidden after logout\n    at /var/www/360lm/tests/stores.spec.js:51:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:52:54.613Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-dd720-n-screen-and-clears-session-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-dd720-n-screen-and-clears-session-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 51
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-c6188b8a2ede362940c5",
              "file": "stores.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "after logout, stores_user removed from localStorage",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1786,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8300,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:60:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 60
                        },
                        "snippet": "  58 |   test('after logout, stores_user removed from localStorage', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  61 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  62 |     await page.waitForSelector('#screen-login', { timeout: 4000 });\n  63 |     const stored = await page.evaluate(() => localStorage.getItem('stores_user'));"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 60
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  58 |   test('after logout, stores_user removed from localStorage', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  61 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  62 |     await page.waitForSelector('#screen-login', { timeout: 4000 });\n  63 |     const stored = await page.evaluate(() => localStorage.getItem('stores_user'));\n    at /var/www/360lm/tests/stores.spec.js:60:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:53:04.376Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-cce29-r-removed-from-localStorage-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-cce29-r-removed-from-localStorage-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 60
                      }
                    },
                    {
                      "workerIndex": 1787,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8265,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:60:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 60
                        },
                        "snippet": "  58 |   test('after logout, stores_user removed from localStorage', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  61 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  62 |     await page.waitForSelector('#screen-login', { timeout: 4000 });\n  63 |     const stored = await page.evaluate(() => localStorage.getItem('stores_user'));"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 60
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  58 |   test('after logout, stores_user removed from localStorage', async ({ page }) => {\n  59 |     await page.goto(BASE_URL);\n> 60 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  61 |     await page.locator('#screen-home .hdr .stores-logout').click();\n  62 |     await page.waitForSelector('#screen-login', { timeout: 4000 });\n  63 |     const stored = await page.evaluate(() => localStorage.getItem('stores_user'));\n    at /var/www/360lm/tests/stores.spec.js:60:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:53:13.781Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-cce29-r-removed-from-localStorage-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-H-cce29-r-removed-from-localStorage-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 60
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-5c5d3076d0af90d8ad77",
              "file": "stores.spec.js",
              "line": 58,
              "column": 3
            }
          ]
        },
        {
          "title": "Stores — User Bar: Non-Home Screens",
          "file": "stores.spec.js",
          "line": 68,
          "column": 6,
          "specs": [
            {
              "title": "stock screen hdr has user name span",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 780,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8257,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:75:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 75
                        },
                        "snippet": "  73 |   test('stock screen hdr has user name span', async ({ page }) => {\n  74 |     await page.goto(BASE_URL);\n> 75 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  76 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  77 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  78 |     await expect(page.locator('#screen-stock .hdr .hdr-user')).toHaveText(/Harish/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 75
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  73 |   test('stock screen hdr has user name span', async ({ page }) => {\n  74 |     await page.goto(BASE_URL);\n> 75 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  76 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  77 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  78 |     await expect(page.locator('#screen-stock .hdr .hdr-user')).toHaveText(/Harish/);\n    at /var/www/360lm/tests/stores.spec.js:75:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:53:20.411Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-fa354-reen-hdr-has-user-name-span-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-fa354-reen-hdr-has-user-name-span-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 75
                      }
                    },
                    {
                      "workerIndex": 781,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8243,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:75:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 75
                        },
                        "snippet": "  73 |   test('stock screen hdr has user name span', async ({ page }) => {\n  74 |     await page.goto(BASE_URL);\n> 75 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  76 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  77 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  78 |     await expect(page.locator('#screen-stock .hdr .hdr-user')).toHaveText(/Harish/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 75
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  73 |   test('stock screen hdr has user name span', async ({ page }) => {\n  74 |     await page.goto(BASE_URL);\n> 75 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  76 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  77 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  78 |     await expect(page.locator('#screen-stock .hdr .hdr-user')).toHaveText(/Harish/);\n    at /var/www/360lm/tests/stores.spec.js:75:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:53:29.629Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-fa354-reen-hdr-has-user-name-span-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-fa354-reen-hdr-has-user-name-span-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 75
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-5d45f4e48aac965099fa",
              "file": "stores.spec.js",
              "line": 73,
              "column": 3
            },
            {
              "title": "stock screen hdr has Logout button",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 782,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8236,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:83:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 83
                        },
                        "snippet": "  81 |   test('stock screen hdr has Logout button', async ({ page }) => {\n  82 |     await page.goto(BASE_URL);\n> 83 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  84 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  85 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  86 |     await expect(page.locator('#screen-stock .hdr .stores-logout')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 83
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  81 |   test('stock screen hdr has Logout button', async ({ page }) => {\n  82 |     await page.goto(BASE_URL);\n> 83 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  84 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  85 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  86 |     await expect(page.locator('#screen-stock .hdr .stores-logout')).toBeVisible();\n    at /var/www/360lm/tests/stores.spec.js:83:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:53:38.853Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-df05e-creen-hdr-has-Logout-button-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-df05e-creen-hdr-has-Logout-button-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 83
                      }
                    },
                    {
                      "workerIndex": 783,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8280,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:83:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 83
                        },
                        "snippet": "  81 |   test('stock screen hdr has Logout button', async ({ page }) => {\n  82 |     await page.goto(BASE_URL);\n> 83 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  84 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  85 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  86 |     await expect(page.locator('#screen-stock .hdr .stores-logout')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 83
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  81 |   test('stock screen hdr has Logout button', async ({ page }) => {\n  82 |     await page.goto(BASE_URL);\n> 83 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  84 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  85 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  86 |     await expect(page.locator('#screen-stock .hdr .stores-logout')).toBeVisible();\n    at /var/www/360lm/tests/stores.spec.js:83:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:53:48.079Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-df05e-creen-hdr-has-Logout-button-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-df05e-creen-hdr-has-Logout-button-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 83
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-d47e7c5c29cebf9d54ff",
              "file": "stores.spec.js",
              "line": 81,
              "column": 3
            },
            {
              "title": "logout from non-home screen shows login",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 784,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8266,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:91:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 91
                        },
                        "snippet": "  89 |   test('logout from non-home screen shows login', async ({ page }) => {\n  90 |     await page.goto(BASE_URL);\n> 91 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  92 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  93 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  94 |     await page.locator('#screen-stock .hdr .stores-logout').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 91
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  89 |   test('logout from non-home screen shows login', async ({ page }) => {\n  90 |     await page.goto(BASE_URL);\n> 91 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  92 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  93 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  94 |     await page.locator('#screen-stock .hdr .stores-logout').click();\n    at /var/www/360lm/tests/stores.spec.js:91:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:53:57.347Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-a6857-non-home-screen-shows-login-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-a6857-non-home-screen-shows-login-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 91
                      }
                    },
                    {
                      "workerIndex": 785,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8264,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:91:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 91
                        },
                        "snippet": "  89 |   test('logout from non-home screen shows login', async ({ page }) => {\n  90 |     await page.goto(BASE_URL);\n> 91 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  92 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  93 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  94 |     await page.locator('#screen-stock .hdr .stores-logout').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 91
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  89 |   test('logout from non-home screen shows login', async ({ page }) => {\n  90 |     await page.goto(BASE_URL);\n> 91 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  92 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  93 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  94 |     await page.locator('#screen-stock .hdr .stores-logout').click();\n    at /var/www/360lm/tests/stores.spec.js:91:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:54:06.645Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-a6857-non-home-screen-shows-login-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-a6857-non-home-screen-shows-login-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 91
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-427b009ee28fa4a6c369",
              "file": "stores.spec.js",
              "line": 89,
              "column": 3
            },
            {
              "title": "existing Hub button still present on stock screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 786,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8238,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:100:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 100
                        },
                        "snippet": "   98 |   test('existing Hub button still present on stock screen', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  101 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  102 |     await expect(page.locator('#screen-stock .hdr .btn-icon[title=\"Hub\"]')).toBeVisible({ timeout: 4000 });\n  103 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 100
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n   98 |   test('existing Hub button still present on stock screen', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  101 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  102 |     await expect(page.locator('#screen-stock .hdr .btn-icon[title=\"Hub\"]')).toBeVisible({ timeout: 4000 });\n  103 |   });\n    at /var/www/360lm/tests/stores.spec.js:100:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:54:15.881Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-c2fed-ill-present-on-stock-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-c2fed-ill-present-on-stock-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 100
                      }
                    },
                    {
                      "workerIndex": 787,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8243,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:100:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 100
                        },
                        "snippet": "   98 |   test('existing Hub button still present on stock screen', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  101 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  102 |     await expect(page.locator('#screen-stock .hdr .btn-icon[title=\"Hub\"]')).toBeVisible({ timeout: 4000 });\n  103 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 100
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n   98 |   test('existing Hub button still present on stock screen', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  101 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  102 |     await expect(page.locator('#screen-stock .hdr .btn-icon[title=\"Hub\"]')).toBeVisible({ timeout: 4000 });\n  103 |   });\n    at /var/www/360lm/tests/stores.spec.js:100:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:54:25.109Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-c2fed-ill-present-on-stock-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-c2fed-ill-present-on-stock-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 100
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-67179e54effac284ca31",
              "file": "stores.spec.js",
              "line": 98,
              "column": 3
            },
            {
              "title": "stock screen hdr has user name span",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1788,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8258,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:75:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 75
                        },
                        "snippet": "  73 |   test('stock screen hdr has user name span', async ({ page }) => {\n  74 |     await page.goto(BASE_URL);\n> 75 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  76 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  77 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  78 |     await expect(page.locator('#screen-stock .hdr .hdr-user')).toHaveText(/Harish/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 75
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  73 |   test('stock screen hdr has user name span', async ({ page }) => {\n  74 |     await page.goto(BASE_URL);\n> 75 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  76 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  77 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  78 |     await expect(page.locator('#screen-stock .hdr .hdr-user')).toHaveText(/Harish/);\n    at /var/www/360lm/tests/stores.spec.js:75:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:53:23.017Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-fa354-reen-hdr-has-user-name-span-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-fa354-reen-hdr-has-user-name-span-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 75
                      }
                    },
                    {
                      "workerIndex": 1789,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8250,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:75:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 75
                        },
                        "snippet": "  73 |   test('stock screen hdr has user name span', async ({ page }) => {\n  74 |     await page.goto(BASE_URL);\n> 75 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  76 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  77 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  78 |     await expect(page.locator('#screen-stock .hdr .hdr-user')).toHaveText(/Harish/);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 75
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  73 |   test('stock screen hdr has user name span', async ({ page }) => {\n  74 |     await page.goto(BASE_URL);\n> 75 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  76 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  77 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  78 |     await expect(page.locator('#screen-stock .hdr .hdr-user')).toHaveText(/Harish/);\n    at /var/www/360lm/tests/stores.spec.js:75:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:53:32.208Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-fa354-reen-hdr-has-user-name-span-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-fa354-reen-hdr-has-user-name-span-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 75
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-15b26c3afb97d7f1d29d",
              "file": "stores.spec.js",
              "line": 73,
              "column": 3
            },
            {
              "title": "stock screen hdr has Logout button",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1790,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8241,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:83:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 83
                        },
                        "snippet": "  81 |   test('stock screen hdr has Logout button', async ({ page }) => {\n  82 |     await page.goto(BASE_URL);\n> 83 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  84 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  85 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  86 |     await expect(page.locator('#screen-stock .hdr .stores-logout')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 83
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  81 |   test('stock screen hdr has Logout button', async ({ page }) => {\n  82 |     await page.goto(BASE_URL);\n> 83 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  84 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  85 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  86 |     await expect(page.locator('#screen-stock .hdr .stores-logout')).toBeVisible();\n    at /var/www/360lm/tests/stores.spec.js:83:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:53:41.406Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-df05e-creen-hdr-has-Logout-button-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-df05e-creen-hdr-has-Logout-button-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 83
                      }
                    },
                    {
                      "workerIndex": 1791,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8254,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:83:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 83
                        },
                        "snippet": "  81 |   test('stock screen hdr has Logout button', async ({ page }) => {\n  82 |     await page.goto(BASE_URL);\n> 83 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  84 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  85 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  86 |     await expect(page.locator('#screen-stock .hdr .stores-logout')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 83
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  81 |   test('stock screen hdr has Logout button', async ({ page }) => {\n  82 |     await page.goto(BASE_URL);\n> 83 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  84 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  85 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  86 |     await expect(page.locator('#screen-stock .hdr .stores-logout')).toBeVisible();\n    at /var/www/360lm/tests/stores.spec.js:83:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:53:50.627Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-df05e-creen-hdr-has-Logout-button-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-df05e-creen-hdr-has-Logout-button-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 83
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-aeadfb663a48d8e1996f",
              "file": "stores.spec.js",
              "line": 81,
              "column": 3
            },
            {
              "title": "logout from non-home screen shows login",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1792,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8252,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:91:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 91
                        },
                        "snippet": "  89 |   test('logout from non-home screen shows login', async ({ page }) => {\n  90 |     await page.goto(BASE_URL);\n> 91 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  92 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  93 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  94 |     await page.locator('#screen-stock .hdr .stores-logout').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 91
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  89 |   test('logout from non-home screen shows login', async ({ page }) => {\n  90 |     await page.goto(BASE_URL);\n> 91 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  92 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  93 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  94 |     await page.locator('#screen-stock .hdr .stores-logout').click();\n    at /var/www/360lm/tests/stores.spec.js:91:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:53:59.926Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-a6857-non-home-screen-shows-login-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-a6857-non-home-screen-shows-login-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 91
                      }
                    },
                    {
                      "workerIndex": 1793,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8228,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:91:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 91
                        },
                        "snippet": "  89 |   test('logout from non-home screen shows login', async ({ page }) => {\n  90 |     await page.goto(BASE_URL);\n> 91 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  92 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  93 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  94 |     await page.locator('#screen-stock .hdr .stores-logout').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 91
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  89 |   test('logout from non-home screen shows login', async ({ page }) => {\n  90 |     await page.goto(BASE_URL);\n> 91 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n     |                ^\n  92 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  93 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  94 |     await page.locator('#screen-stock .hdr .stores-logout').click();\n    at /var/www/360lm/tests/stores.spec.js:91:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:54:09.158Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-a6857-non-home-screen-shows-login-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-a6857-non-home-screen-shows-login-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 91
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-f2bafe0050a2389bcdf7",
              "file": "stores.spec.js",
              "line": 89,
              "column": 3
            },
            {
              "title": "existing Hub button still present on stock screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1794,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8216,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:100:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 100
                        },
                        "snippet": "   98 |   test('existing Hub button still present on stock screen', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  101 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  102 |     await expect(page.locator('#screen-stock .hdr .btn-icon[title=\"Hub\"]')).toBeVisible({ timeout: 4000 });\n  103 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 100
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n   98 |   test('existing Hub button still present on stock screen', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  101 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  102 |     await expect(page.locator('#screen-stock .hdr .btn-icon[title=\"Hub\"]')).toBeVisible({ timeout: 4000 });\n  103 |   });\n    at /var/www/360lm/tests/stores.spec.js:100:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:54:18.339Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-c2fed-ill-present-on-stock-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-c2fed-ill-present-on-stock-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 100
                      }
                    },
                    {
                      "workerIndex": 1795,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8223,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:100:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 100
                        },
                        "snippet": "   98 |   test('existing Hub button still present on stock screen', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  101 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  102 |     await expect(page.locator('#screen-stock .hdr .btn-icon[title=\"Hub\"]')).toBeVisible({ timeout: 4000 });\n  103 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 100
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n   98 |   test('existing Hub button still present on stock screen', async ({ page }) => {\n   99 |     await page.goto(BASE_URL);\n> 100 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  101 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  102 |     await expect(page.locator('#screen-stock .hdr .btn-icon[title=\"Hub\"]')).toBeVisible({ timeout: 4000 });\n  103 |   });\n    at /var/www/360lm/tests/stores.spec.js:100:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:54:27.526Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-c2fed-ill-present-on-stock-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-User-Bar-N-c2fed-ill-present-on-stock-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 100
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-a97ac33584d859a1c058",
              "file": "stores.spec.js",
              "line": 98,
              "column": 3
            }
          ]
        },
        {
          "title": "Stores — Existing Functionality Unchanged",
          "file": "stores.spec.js",
          "line": 106,
          "column": 6,
          "specs": [
            {
              "title": "login screen has employee selector and PIN pad",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 788,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8258,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:109:46",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 46,
                          "line": 109
                        },
                        "snippet": "  107 |   test('login screen has employee selector and PIN pad', async ({ page }) => {\n  108 |     await page.goto(BASE_URL);\n> 109 |     await expect(page.locator('#login-emp')).toBeVisible({ timeout: 8000 });\n      |                                              ^\n  110 |     await expect(page.locator('.pin-pad')).toBeVisible();\n  111 |   });\n  112 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 46,
                            "line": 109
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n\n\n  107 |   test('login screen has employee selector and PIN pad', async ({ page }) => {\n  108 |     await page.goto(BASE_URL);\n> 109 |     await expect(page.locator('#login-emp')).toBeVisible({ timeout: 8000 });\n      |                                              ^\n  110 |     await expect(page.locator('.pin-pad')).toBeVisible();\n  111 |   });\n  112 |\n    at /var/www/360lm/tests/stores.spec.js:109:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:54:34.268Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-e4495-ployee-selector-and-PIN-pad-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-e4495-ployee-selector-and-PIN-pad-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 46,
                        "line": 109
                      }
                    },
                    {
                      "workerIndex": 789,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8246,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:109:46",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 46,
                          "line": 109
                        },
                        "snippet": "  107 |   test('login screen has employee selector and PIN pad', async ({ page }) => {\n  108 |     await page.goto(BASE_URL);\n> 109 |     await expect(page.locator('#login-emp')).toBeVisible({ timeout: 8000 });\n      |                                              ^\n  110 |     await expect(page.locator('.pin-pad')).toBeVisible();\n  111 |   });\n  112 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 46,
                            "line": 109
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n\n\n  107 |   test('login screen has employee selector and PIN pad', async ({ page }) => {\n  108 |     await page.goto(BASE_URL);\n> 109 |     await expect(page.locator('#login-emp')).toBeVisible({ timeout: 8000 });\n      |                                              ^\n  110 |     await expect(page.locator('.pin-pad')).toBeVisible();\n  111 |   });\n  112 |\n    at /var/www/360lm/tests/stores.spec.js:109:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:54:43.511Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-e4495-ployee-selector-and-PIN-pad-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-e4495-ployee-selector-and-PIN-pad-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 46,
                        "line": 109
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-528d025e9e5824e54a75",
              "file": "stores.spec.js",
              "line": 107,
              "column": 3
            },
            {
              "title": "bottom nav shows correct tabs for admin user",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 790,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8281,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:116:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 116
                        },
                        "snippet": "  114 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  117 |     await expect(page.locator('#main-nav')).toBeVisible();\n  118 |     const navCount = await page.locator('.nav-btn').count();\n  119 |     expect(navCount).toBeGreaterThanOrEqual(4);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 116
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  114 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  117 |     await expect(page.locator('#main-nav')).toBeVisible();\n  118 |     const navCount = await page.locator('.nav-btn').count();\n  119 |     expect(navCount).toBeGreaterThanOrEqual(4);\n    at /var/www/360lm/tests/stores.spec.js:116:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:54:52.750Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-45deb-correct-tabs-for-admin-user-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-45deb-correct-tabs-for-admin-user-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 116
                      }
                    },
                    {
                      "workerIndex": 791,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8448,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:116:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 116
                        },
                        "snippet": "  114 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  117 |     await expect(page.locator('#main-nav')).toBeVisible();\n  118 |     const navCount = await page.locator('.nav-btn').count();\n  119 |     expect(navCount).toBeGreaterThanOrEqual(4);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 116
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  114 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  117 |     await expect(page.locator('#main-nav')).toBeVisible();\n  118 |     const navCount = await page.locator('.nav-btn').count();\n  119 |     expect(navCount).toBeGreaterThanOrEqual(4);\n    at /var/www/360lm/tests/stores.spec.js:116:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:55:02.967Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-45deb-correct-tabs-for-admin-user-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-45deb-correct-tabs-for-admin-user-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 116
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-b86960d7f735b3a60919",
              "file": "stores.spec.js",
              "line": 113,
              "column": 3
            },
            {
              "title": "stock list loads on Stock tab",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 792,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8272,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:125:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 125
                        },
                        "snippet": "  123 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  124 |     await page.goto(BASE_URL);\n> 125 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  126 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  127 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  128 |     await expect(page.locator('#stock-list')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 125
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  123 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  124 |     await page.goto(BASE_URL);\n> 125 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  126 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  127 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  128 |     await expect(page.locator('#stock-list')).toBeVisible();\n    at /var/www/360lm/tests/stores.spec.js:125:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:55:12.489Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-6462c-ock-list-loads-on-Stock-tab-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-6462c-ock-list-loads-on-Stock-tab-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 125
                      }
                    },
                    {
                      "workerIndex": 793,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8253,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:125:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 125
                        },
                        "snippet": "  123 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  124 |     await page.goto(BASE_URL);\n> 125 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  126 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  127 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  128 |     await expect(page.locator('#stock-list')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 125
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  123 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  124 |     await page.goto(BASE_URL);\n> 125 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  126 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  127 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  128 |     await expect(page.locator('#stock-list')).toBeVisible();\n    at /var/www/360lm/tests/stores.spec.js:125:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:55:21.756Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-6462c-ock-list-loads-on-Stock-tab-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-6462c-ock-list-loads-on-Stock-tab-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 125
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-0d2608a51c5dc66dba60",
              "file": "stores.spec.js",
              "line": 122,
              "column": 3
            },
            {
              "title": "login screen has employee selector and PIN pad",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1796,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8242,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:109:46",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 46,
                          "line": 109
                        },
                        "snippet": "  107 |   test('login screen has employee selector and PIN pad', async ({ page }) => {\n  108 |     await page.goto(BASE_URL);\n> 109 |     await expect(page.locator('#login-emp')).toBeVisible({ timeout: 8000 });\n      |                                              ^\n  110 |     await expect(page.locator('.pin-pad')).toBeVisible();\n  111 |   });\n  112 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 46,
                            "line": 109
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n\n\n  107 |   test('login screen has employee selector and PIN pad', async ({ page }) => {\n  108 |     await page.goto(BASE_URL);\n> 109 |     await expect(page.locator('#login-emp')).toBeVisible({ timeout: 8000 });\n      |                                              ^\n  110 |     await expect(page.locator('.pin-pad')).toBeVisible();\n  111 |   });\n  112 |\n    at /var/www/360lm/tests/stores.spec.js:109:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:54:36.694Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-e4495-ployee-selector-and-PIN-pad-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-e4495-ployee-selector-and-PIN-pad-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 46,
                        "line": 109
                      }
                    },
                    {
                      "workerIndex": 1797,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8254,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:109:46",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 46,
                          "line": 109
                        },
                        "snippet": "  107 |   test('login screen has employee selector and PIN pad', async ({ page }) => {\n  108 |     await page.goto(BASE_URL);\n> 109 |     await expect(page.locator('#login-emp')).toBeVisible({ timeout: 8000 });\n      |                                              ^\n  110 |     await expect(page.locator('.pin-pad')).toBeVisible();\n  111 |   });\n  112 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 46,
                            "line": 109
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#login-emp')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#login-emp')\u001b[22m\n\n\n  107 |   test('login screen has employee selector and PIN pad', async ({ page }) => {\n  108 |     await page.goto(BASE_URL);\n> 109 |     await expect(page.locator('#login-emp')).toBeVisible({ timeout: 8000 });\n      |                                              ^\n  110 |     await expect(page.locator('.pin-pad')).toBeVisible();\n  111 |   });\n  112 |\n    at /var/www/360lm/tests/stores.spec.js:109:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:54:45.854Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-e4495-ployee-selector-and-PIN-pad-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-e4495-ployee-selector-and-PIN-pad-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 46,
                        "line": 109
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-b893715e37c512038364",
              "file": "stores.spec.js",
              "line": 107,
              "column": 3
            },
            {
              "title": "bottom nav shows correct tabs for admin user",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1798,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8341,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:116:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 116
                        },
                        "snippet": "  114 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  117 |     await expect(page.locator('#main-nav')).toBeVisible();\n  118 |     const navCount = await page.locator('.nav-btn').count();\n  119 |     expect(navCount).toBeGreaterThanOrEqual(4);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 116
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  114 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  117 |     await expect(page.locator('#main-nav')).toBeVisible();\n  118 |     const navCount = await page.locator('.nav-btn').count();\n  119 |     expect(navCount).toBeGreaterThanOrEqual(4);\n    at /var/www/360lm/tests/stores.spec.js:116:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:54:55.071Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-45deb-correct-tabs-for-admin-user-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-45deb-correct-tabs-for-admin-user-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 116
                      }
                    },
                    {
                      "workerIndex": 1799,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8286,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:116:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 116
                        },
                        "snippet": "  114 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  117 |     await expect(page.locator('#main-nav')).toBeVisible();\n  118 |     const navCount = await page.locator('.nav-btn').count();\n  119 |     expect(navCount).toBeGreaterThanOrEqual(4);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 116
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  114 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  117 |     await expect(page.locator('#main-nav')).toBeVisible();\n  118 |     const navCount = await page.locator('.nav-btn').count();\n  119 |     expect(navCount).toBeGreaterThanOrEqual(4);\n    at /var/www/360lm/tests/stores.spec.js:116:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:55:04.984Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-45deb-correct-tabs-for-admin-user-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-45deb-correct-tabs-for-admin-user-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 116
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-5dae1faa2ef5b5dc247d",
              "file": "stores.spec.js",
              "line": 113,
              "column": 3
            },
            {
              "title": "stock list loads on Stock tab",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1800,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8224,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:125:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 125
                        },
                        "snippet": "  123 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  124 |     await page.goto(BASE_URL);\n> 125 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  126 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  127 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  128 |     await expect(page.locator('#stock-list')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 125
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  123 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  124 |     await page.goto(BASE_URL);\n> 125 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  126 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  127 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  128 |     await expect(page.locator('#stock-list')).toBeVisible();\n    at /var/www/360lm/tests/stores.spec.js:125:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:55:14.262Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-6462c-ock-list-loads-on-Stock-tab-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-6462c-ock-list-loads-on-Stock-tab-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 125
                      }
                    },
                    {
                      "workerIndex": 1801,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8258,
                      "error": {
                        "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n",
                        "stack": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n    at /var/www/360lm/tests/stores.spec.js:125:16",
                        "location": {
                          "file": "/var/www/360lm/tests/stores.spec.js",
                          "column": 16,
                          "line": 125
                        },
                        "snippet": "  123 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  124 |     await page.goto(BASE_URL);\n> 125 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  126 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  127 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  128 |     await expect(page.locator('#stock-list')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/stores.spec.js",
                            "column": 16,
                            "line": 125
                          },
                          "message": "TimeoutError: page.waitForSelector: Timeout 8000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#screen-home.active') to be visible\u001b[22m\n\n\n  123 |     await injectStoresUser(page, 'harish', 'Harish Kumar Lal');\n  124 |     await page.goto(BASE_URL);\n> 125 |     await page.waitForSelector('#screen-home.active', { timeout: 8000 });\n      |                ^\n  126 |     await page.locator('.nav-btn:has-text(\"Stock\")').click();\n  127 |     await expect(page.locator('#screen-stock.active')).toBeVisible({ timeout: 4000 });\n  128 |     await expect(page.locator('#stock-list')).toBeVisible();\n    at /var/www/360lm/tests/stores.spec.js:125:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:55:23.367Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-6462c-ock-list-loads-on-Stock-tab-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/stores-Stores-—-Existing-F-6462c-ock-list-loads-on-Stock-tab-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/stores.spec.js",
                        "column": 16,
                        "line": 125
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "26d121da56c5994b87c0-c96864184a1475b812ea",
              "file": "stores.spec.js",
              "line": 122,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "tour_pg_sharing.spec.js",
      "file": "tour_pg_sharing.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Tour Playground — Phase 4.17 org-wide sharing + Dexie search",
          "file": "tour_pg_sharing.spec.js",
          "line": 70,
          "column": 6,
          "specs": [
            {
              "title": "User A saves a SHARED tour via the real Save modal (unchecked = shared by default)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 794,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6229,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:91:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 91
                        },
                        "snippet": "  89 |     await page.fill('#inp-save-tour-title', 'PW-TEST-SHARED');\n  90 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 91 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n     |                                          ^\n  92 |   });\n  93 |\n  94 |   test('User A saves a PRIVATE tour via the modal (checkbox checked)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 91
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  89 |     await page.fill('#inp-save-tour-title', 'PW-TEST-SHARED');\n  90 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 91 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n     |                                          ^\n  92 |   });\n  93 |\n  94 |   test('User A saves a PRIVATE tour via the modal (checkbox checked)', async ({ page }) => {\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:91:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:55:30.961Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 91
                      }
                    },
                    {
                      "workerIndex": 795,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6143,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:91:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 91
                        },
                        "snippet": "  89 |     await page.fill('#inp-save-tour-title', 'PW-TEST-SHARED');\n  90 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 91 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n     |                                          ^\n  92 |   });\n  93 |\n  94 |   test('User A saves a PRIVATE tour via the modal (checkbox checked)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 91
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  89 |     await page.fill('#inp-save-tour-title', 'PW-TEST-SHARED');\n  90 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 91 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n     |                                          ^\n  92 |   });\n  93 |\n  94 |   test('User A saves a PRIVATE tour via the modal (checkbox checked)', async ({ page }) => {\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:91:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:55:38.378Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 91
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-f6222ca1f7334beb5de1",
              "file": "tour_pg_sharing.spec.js",
              "line": 82,
              "column": 3
            },
            {
              "title": "User A saves a PRIVATE tour via the modal (checkbox checked)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 796,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6182,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:102:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 102
                        },
                        "snippet": "  100 |     await page.check('#chk-save-tour-private');\n  101 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 102 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  103 |   });\n  104 |\n  105 |   test('User B sees the SHARED tour with \"Created by\" badge, no delete button, but NOT the private one', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 102
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  100 |     await page.check('#chk-save-tour-private');\n  101 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 102 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  103 |   });\n  104 |\n  105 |   test('User B sees the SHARED tour with \"Created by\" badge, no delete button, but NOT the private one', async ({ page }) => {\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:102:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:55:45.659Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 102
                      }
                    },
                    {
                      "workerIndex": 797,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6197,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:102:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 102
                        },
                        "snippet": "  100 |     await page.check('#chk-save-tour-private');\n  101 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 102 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  103 |   });\n  104 |\n  105 |   test('User B sees the SHARED tour with \"Created by\" badge, no delete button, but NOT the private one', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 102
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  100 |     await page.check('#chk-save-tour-private');\n  101 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 102 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  103 |   });\n  104 |\n  105 |   test('User B sees the SHARED tour with \"Created by\" badge, no delete button, but NOT the private one', async ({ page }) => {\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:102:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:55:52.949Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 102
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-97c2a3446214de8b08f3",
              "file": "tour_pg_sharing.spec.js",
              "line": 94,
              "column": 3
            },
            {
              "title": "User B sees the SHARED tour with \"Created by\" badge, no delete button, but NOT the private one",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 798,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6777,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:114:30",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 30,
                          "line": 114
                        },
                        "snippet": "  112 |\n  113 |     const sharedItem = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n> 114 |     await expect(sharedItem).toBeVisible();\n      |                              ^\n  115 |     await expect(sharedItem).toContainText('Created by PW Test Alice');\n  116 |     await expect(sharedItem.locator('.hist-btn.danger')).toHaveCount(0); // no delete for non-owner\n  117 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 30,
                            "line": 114
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n\n  112 |\n  113 |     const sharedItem = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n> 114 |     await expect(sharedItem).toBeVisible();\n      |                              ^\n  115 |     await expect(sharedItem).toContainText('Created by PW Test Alice');\n  116 |     await expect(sharedItem.locator('.hist-btn.danger')).toHaveCount(0); // no delete for non-owner\n  117 |\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:114:30"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:56:00.327Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 30,
                        "line": 114
                      }
                    },
                    {
                      "workerIndex": 799,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6570,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:114:30",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 30,
                          "line": 114
                        },
                        "snippet": "  112 |\n  113 |     const sharedItem = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n> 114 |     await expect(sharedItem).toBeVisible();\n      |                              ^\n  115 |     await expect(sharedItem).toContainText('Created by PW Test Alice');\n  116 |     await expect(sharedItem.locator('.hist-btn.danger')).toHaveCount(0); // no delete for non-owner\n  117 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 30,
                            "line": 114
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n\n  112 |\n  113 |     const sharedItem = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n> 114 |     await expect(sharedItem).toBeVisible();\n      |                              ^\n  115 |     await expect(sharedItem).toContainText('Created by PW Test Alice');\n  116 |     await expect(sharedItem.locator('.hist-btn.danger')).toHaveCount(0); // no delete for non-owner\n  117 |\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:114:30"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:56:08.273Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 30,
                        "line": 114
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-5915cf5027c168b9f064",
              "file": "tour_pg_sharing.spec.js",
              "line": 105,
              "column": 3
            },
            {
              "title": "User B's Dexie search filters to the shared tour, including by creator name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 800,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6740,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:131:63",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 63,
                          "line": 131
                        },
                        "snippet": "  129 |     await page.fill('#search-tours', 'PW-TEST-SHARED');\n  130 |     await page.waitForTimeout(200);\n> 131 |     await expect(page.locator('#hist-tours-list .hist-item')).toHaveCount(1);\n      |                                                               ^\n  132 |     await expect(page.locator('#hist-tours-list .hist-item')).toContainText('PW-TEST-SHARED');\n  133 |\n  134 |     // Search by the creator's name instead of the title"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 63,
                            "line": 131
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  129 |     await page.fill('#search-tours', 'PW-TEST-SHARED');\n  130 |     await page.waitForTimeout(200);\n> 131 |     await expect(page.locator('#hist-tours-list .hist-item')).toHaveCount(1);\n      |                                                               ^\n  132 |     await expect(page.locator('#hist-tours-list .hist-item')).toContainText('PW-TEST-SHARED');\n  133 |\n  134 |     // Search by the creator's name instead of the title\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:131:63"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:56:15.948Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 63,
                        "line": 131
                      }
                    },
                    {
                      "workerIndex": 801,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6783,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:131:63",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 63,
                          "line": 131
                        },
                        "snippet": "  129 |     await page.fill('#search-tours', 'PW-TEST-SHARED');\n  130 |     await page.waitForTimeout(200);\n> 131 |     await expect(page.locator('#hist-tours-list .hist-item')).toHaveCount(1);\n      |                                                               ^\n  132 |     await expect(page.locator('#hist-tours-list .hist-item')).toContainText('PW-TEST-SHARED');\n  133 |\n  134 |     // Search by the creator's name instead of the title"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 63,
                            "line": 131
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  129 |     await page.fill('#search-tours', 'PW-TEST-SHARED');\n  130 |     await page.waitForTimeout(200);\n> 131 |     await expect(page.locator('#hist-tours-list .hist-item')).toHaveCount(1);\n      |                                                               ^\n  132 |     await expect(page.locator('#hist-tours-list .hist-item')).toContainText('PW-TEST-SHARED');\n  133 |\n  134 |     // Search by the creator's name instead of the title\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:131:63"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:56:23.737Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 63,
                        "line": 131
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-100b4d829cbaf662b906",
              "file": "tour_pg_sharing.spec.js",
              "line": 121,
              "column": 3
            },
            {
              "title": "multi-token search: \"BTC himach\" matches a title with both words, not adjacent",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 802,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6150,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:151:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 151
                        },
                        "snippet": "  149 |     await page.fill('#inp-save-tour-title', 'PW-TEST-Verve-BTC-Batch-Himachal');\n  150 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 151 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  152 |\n  153 |     await page.goto(BASE_URL);\n  154 |     await page.waitForTimeout(300);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 151
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  149 |     await page.fill('#inp-save-tour-title', 'PW-TEST-Verve-BTC-Batch-Himachal');\n  150 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 151 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  152 |\n  153 |     await page.goto(BASE_URL);\n  154 |     await page.waitForTimeout(300);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:151:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:56:31.558Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 151
                      }
                    },
                    {
                      "workerIndex": 803,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6192,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:151:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 151
                        },
                        "snippet": "  149 |     await page.fill('#inp-save-tour-title', 'PW-TEST-Verve-BTC-Batch-Himachal');\n  150 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 151 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  152 |\n  153 |     await page.goto(BASE_URL);\n  154 |     await page.waitForTimeout(300);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 151
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  149 |     await page.fill('#inp-save-tour-title', 'PW-TEST-Verve-BTC-Batch-Himachal');\n  150 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 151 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  152 |\n  153 |     await page.goto(BASE_URL);\n  154 |     await page.waitForTimeout(300);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:151:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:56:38.816Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 151
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-5d5ee80755901cca30ca",
              "file": "tour_pg_sharing.spec.js",
              "line": 145,
              "column": 3
            },
            {
              "title": "multi-token search on Uploads tab: \"BTC himach\" matches a filename with both words",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 804,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7302,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:186:114",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 114,
                          "line": 186
                        },
                        "snippet": "  184 |     await page.fill('#search-uploads', 'BTC himach');\n  185 |     await page.waitForTimeout(200);\n> 186 |     await expect(page.locator('#hist-uploads-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  187 |\n  188 |     await page.fill('#search-uploads', 'BTC nonexistentword');\n  189 |     await page.waitForTimeout(200);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 114,
                            "line": 186
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n\n  184 |     await page.fill('#search-uploads', 'BTC himach');\n  185 |     await page.waitForTimeout(200);\n> 186 |     await expect(page.locator('#hist-uploads-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  187 |\n  188 |     await page.fill('#search-uploads', 'BTC nonexistentword');\n  189 |     await page.waitForTimeout(200);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:186:114"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:56:46.101Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 114,
                        "line": 186
                      }
                    },
                    {
                      "workerIndex": 805,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7536,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:186:114",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 114,
                          "line": 186
                        },
                        "snippet": "  184 |     await page.fill('#search-uploads', 'BTC himach');\n  185 |     await page.waitForTimeout(200);\n> 186 |     await expect(page.locator('#hist-uploads-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  187 |\n  188 |     await page.fill('#search-uploads', 'BTC nonexistentword');\n  189 |     await page.waitForTimeout(200);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 114,
                            "line": 186
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n\n  184 |     await page.fill('#search-uploads', 'BTC himach');\n  185 |     await page.waitForTimeout(200);\n> 186 |     await expect(page.locator('#hist-uploads-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  187 |\n  188 |     await page.fill('#search-uploads', 'BTC nonexistentword');\n  189 |     await page.waitForTimeout(200);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:186:114"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:56:54.517Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 114,
                        "line": 186
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-45867a931f82bfce0be0",
              "file": "tour_pg_sharing.spec.js",
              "line": 171,
              "column": 3
            },
            {
              "title": "multi-token search on Batches tab: \"BTC himach\" matches a batch name with both words",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 806,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7382,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:214:114",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 114,
                          "line": 214
                        },
                        "snippet": "  212 |     await page.fill('#search-batches', 'BTC himach');\n  213 |     await page.waitForTimeout(200);\n> 214 |     await expect(page.locator('#hist-batches-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  215 |\n  216 |     await page.fill('#search-batches', 'BTC nonexistentword');\n  217 |     await page.waitForTimeout(200);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 114,
                            "line": 214
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n\n  212 |     await page.fill('#search-batches', 'BTC himach');\n  213 |     await page.waitForTimeout(200);\n> 214 |     await expect(page.locator('#hist-batches-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  215 |\n  216 |     await page.fill('#search-batches', 'BTC nonexistentword');\n  217 |     await page.waitForTimeout(200);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:214:114"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:03.815Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 114,
                        "line": 214
                      }
                    },
                    {
                      "workerIndex": 807,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7265,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:214:114",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 114,
                          "line": 214
                        },
                        "snippet": "  212 |     await page.fill('#search-batches', 'BTC himach');\n  213 |     await page.waitForTimeout(200);\n> 214 |     await expect(page.locator('#hist-batches-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  215 |\n  216 |     await page.fill('#search-batches', 'BTC nonexistentword');\n  217 |     await page.waitForTimeout(200);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 114,
                            "line": 214
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n\n  212 |     await page.fill('#search-batches', 'BTC himach');\n  213 |     await page.waitForTimeout(200);\n> 214 |     await expect(page.locator('#hist-batches-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  215 |\n  216 |     await page.fill('#search-batches', 'BTC nonexistentword');\n  217 |     await page.waitForTimeout(200);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:214:114"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:57:12.336Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 114,
                        "line": 214
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-8cf0f1563b2266b9b21f",
              "file": "tour_pg_sharing.spec.js",
              "line": 193,
              "column": 3
            },
            {
              "title": "User A sees BOTH own tours (shared + private) in their own History, with delete buttons",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 808,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6550,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:231:26",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 26,
                          "line": 231
                        },
                        "snippet": "  229 |     const shared  = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n  230 |     const priv    = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-PRIVATE' });\n> 231 |     await expect(shared).toBeVisible();\n      |                          ^\n  232 |     await expect(priv).toBeVisible();\n  233 |     await expect(shared.locator('.hist-btn.danger')).toHaveCount(1); // owner sees delete\n  234 |     await expect(priv.locator('.hist-btn.danger')).toHaveCount(1);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 26,
                            "line": 231
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n\n  229 |     const shared  = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n  230 |     const priv    = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-PRIVATE' });\n> 231 |     await expect(shared).toBeVisible();\n      |                          ^\n  232 |     await expect(priv).toBeVisible();\n  233 |     await expect(shared.locator('.hist-btn.danger')).toHaveCount(1); // owner sees delete\n  234 |     await expect(priv.locator('.hist-btn.danger')).toHaveCount(1);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:231:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:20.742Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 26,
                        "line": 231
                      }
                    },
                    {
                      "workerIndex": 809,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6514,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:231:26",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 26,
                          "line": 231
                        },
                        "snippet": "  229 |     const shared  = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n  230 |     const priv    = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-PRIVATE' });\n> 231 |     await expect(shared).toBeVisible();\n      |                          ^\n  232 |     await expect(priv).toBeVisible();\n  233 |     await expect(shared.locator('.hist-btn.danger')).toHaveCount(1); // owner sees delete\n  234 |     await expect(priv.locator('.hist-btn.danger')).toHaveCount(1);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 26,
                            "line": 231
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n\n  229 |     const shared  = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n  230 |     const priv    = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-PRIVATE' });\n> 231 |     await expect(shared).toBeVisible();\n      |                          ^\n  232 |     await expect(priv).toBeVisible();\n  233 |     await expect(shared.locator('.hist-btn.danger')).toHaveCount(1); // owner sees delete\n  234 |     await expect(priv.locator('.hist-btn.danger')).toHaveCount(1);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:231:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:57:28.441Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 26,
                        "line": 231
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-be96f8d37bd9be5f118f",
              "file": "tour_pg_sharing.spec.js",
              "line": 221,
              "column": 3
            },
            {
              "title": "Global \"keep my saves private\" toggle persists per-user in localStorage",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 810,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1452,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:36.077Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "7856fccbadda5205b698-5d4472a2e851863289ef",
              "file": "tour_pg_sharing.spec.js",
              "line": 239,
              "column": 3
            },
            {
              "title": "User A saves a SHARED tour via the real Save modal (unchecked = shared by default)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1802,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6123,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:91:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 91
                        },
                        "snippet": "  89 |     await page.fill('#inp-save-tour-title', 'PW-TEST-SHARED');\n  90 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 91 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n     |                                          ^\n  92 |   });\n  93 |\n  94 |   test('User A saves a PRIVATE tour via the modal (checkbox checked)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 91
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  89 |     await page.fill('#inp-save-tour-title', 'PW-TEST-SHARED');\n  90 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 91 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n     |                                          ^\n  92 |   });\n  93 |\n  94 |   test('User A saves a PRIVATE tour via the modal (checkbox checked)', async ({ page }) => {\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:91:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:55:32.551Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 91
                      }
                    },
                    {
                      "workerIndex": 1803,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6188,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:91:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 91
                        },
                        "snippet": "  89 |     await page.fill('#inp-save-tour-title', 'PW-TEST-SHARED');\n  90 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 91 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n     |                                          ^\n  92 |   });\n  93 |\n  94 |   test('User A saves a PRIVATE tour via the modal (checkbox checked)', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 91
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  89 |     await page.fill('#inp-save-tour-title', 'PW-TEST-SHARED');\n  90 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 91 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n     |                                          ^\n  92 |   });\n  93 |\n  94 |   test('User A saves a PRIVATE tour via the modal (checkbox checked)', async ({ page }) => {\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:91:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:55:39.828Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-eaa78-nchecked-shared-by-default--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 91
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-6b3e8c892d988e3de1af",
              "file": "tour_pg_sharing.spec.js",
              "line": 82,
              "column": 3
            },
            {
              "title": "User A saves a PRIVATE tour via the modal (checkbox checked)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1804,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    - locator resolved to <div id=\"toast\"></div>\u001b[22m\n\u001b[2m    - unexpected value \"\"\u001b[22m\n\u001b[2m    9 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    - locator resolved to <div id=\"toast\"></div>\u001b[22m\n\u001b[2m    - unexpected value \"\"\u001b[22m\n\u001b[2m    9 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:102:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 102
                        },
                        "snippet": "  100 |     await page.check('#chk-save-tour-private');\n  101 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 102 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  103 |   });\n  104 |\n  105 |   test('User B sees the SHARED tour with \"Created by\" badge, no delete button, but NOT the private one', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 102
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    - locator resolved to <div id=\"toast\"></div>\u001b[22m\n\u001b[2m    - unexpected value \"\"\u001b[22m\n\u001b[2m    9 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  100 |     await page.check('#chk-save-tour-private');\n  101 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 102 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  103 |   });\n  104 |\n  105 |   test('User B sees the SHARED tour with \"Created by\" badge, no delete button, but NOT the private one', async ({ page }) => {\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:102:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:55:47.142Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 102
                      }
                    },
                    {
                      "workerIndex": 1805,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6214,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:102:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 102
                        },
                        "snippet": "  100 |     await page.check('#chk-save-tour-private');\n  101 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 102 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  103 |   });\n  104 |\n  105 |   test('User B sees the SHARED tour with \"Created by\" badge, no delete button, but NOT the private one', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 102
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    10 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m       - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  100 |     await page.check('#chk-save-tour-private');\n  101 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 102 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  103 |   });\n  104 |\n  105 |   test('User B sees the SHARED tour with \"Created by\" badge, no delete button, but NOT the private one', async ({ page }) => {\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:102:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:55:54.411Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-cf88d-the-modal-checkbox-checked--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 102
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-39060a1b2bee468c0f6d",
              "file": "tour_pg_sharing.spec.js",
              "line": 94,
              "column": 3
            },
            {
              "title": "User B sees the SHARED tour with \"Created by\" badge, no delete button, but NOT the private one",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1806,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6867,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:114:30",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 30,
                          "line": 114
                        },
                        "snippet": "  112 |\n  113 |     const sharedItem = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n> 114 |     await expect(sharedItem).toBeVisible();\n      |                              ^\n  115 |     await expect(sharedItem).toContainText('Created by PW Test Alice');\n  116 |     await expect(sharedItem.locator('.hist-btn.danger')).toHaveCount(0); // no delete for non-owner\n  117 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 30,
                            "line": 114
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n\n  112 |\n  113 |     const sharedItem = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n> 114 |     await expect(sharedItem).toBeVisible();\n      |                              ^\n  115 |     await expect(sharedItem).toContainText('Created by PW Test Alice');\n  116 |     await expect(sharedItem.locator('.hist-btn.danger')).toHaveCount(0); // no delete for non-owner\n  117 |\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:114:30"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:56:02.456Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 30,
                        "line": 114
                      }
                    },
                    {
                      "workerIndex": 1807,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6659,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:114:30",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 30,
                          "line": 114
                        },
                        "snippet": "  112 |\n  113 |     const sharedItem = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n> 114 |     await expect(sharedItem).toBeVisible();\n      |                              ^\n  115 |     await expect(sharedItem).toContainText('Created by PW Test Alice');\n  116 |     await expect(sharedItem.locator('.hist-btn.danger')).toHaveCount(0); // no delete for non-owner\n  117 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 30,
                            "line": 114
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n\n  112 |\n  113 |     const sharedItem = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n> 114 |     await expect(sharedItem).toBeVisible();\n      |                              ^\n  115 |     await expect(sharedItem).toContainText('Created by PW Test Alice');\n  116 |     await expect(sharedItem.locator('.hist-btn.danger')).toHaveCount(0); // no delete for non-owner\n  117 |\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:114:30"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:56:10.630Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-37b60-ton-but-NOT-the-private-one-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 30,
                        "line": 114
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-699a658ded641df7ea32",
              "file": "tour_pg_sharing.spec.js",
              "line": 105,
              "column": 3
            },
            {
              "title": "User B's Dexie search filters to the shared tour, including by creator name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1808,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6845,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:131:63",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 63,
                          "line": 131
                        },
                        "snippet": "  129 |     await page.fill('#search-tours', 'PW-TEST-SHARED');\n  130 |     await page.waitForTimeout(200);\n> 131 |     await expect(page.locator('#hist-tours-list .hist-item')).toHaveCount(1);\n      |                                                               ^\n  132 |     await expect(page.locator('#hist-tours-list .hist-item')).toContainText('PW-TEST-SHARED');\n  133 |\n  134 |     // Search by the creator's name instead of the title"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 63,
                            "line": 131
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  129 |     await page.fill('#search-tours', 'PW-TEST-SHARED');\n  130 |     await page.waitForTimeout(200);\n> 131 |     await expect(page.locator('#hist-tours-list .hist-item')).toHaveCount(1);\n      |                                                               ^\n  132 |     await expect(page.locator('#hist-tours-list .hist-item')).toContainText('PW-TEST-SHARED');\n  133 |\n  134 |     // Search by the creator's name instead of the title\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:131:63"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:56:18.471Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 63,
                        "line": 131
                      }
                    },
                    {
                      "workerIndex": 1809,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6820,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:131:63",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 63,
                          "line": 131
                        },
                        "snippet": "  129 |     await page.fill('#search-tours', 'PW-TEST-SHARED');\n  130 |     await page.waitForTimeout(200);\n> 131 |     await expect(page.locator('#hist-tours-list .hist-item')).toHaveCount(1);\n      |                                                               ^\n  132 |     await expect(page.locator('#hist-tours-list .hist-item')).toContainText('PW-TEST-SHARED');\n  133 |\n  134 |     // Search by the creator's name instead of the title"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 63,
                            "line": 131
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveCount\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator:  locator('#hist-tours-list .hist-item')\nExpected: \u001b[32m1\u001b[39m\nReceived: \u001b[31m0\u001b[39m\nTimeout:  5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveCount\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item')\u001b[22m\n\u001b[2m    14 × locator resolved to 0 elements\u001b[22m\n\u001b[2m       - unexpected value \"0\"\u001b[22m\n\n\n  129 |     await page.fill('#search-tours', 'PW-TEST-SHARED');\n  130 |     await page.waitForTimeout(200);\n> 131 |     await expect(page.locator('#hist-tours-list .hist-item')).toHaveCount(1);\n      |                                                               ^\n  132 |     await expect(page.locator('#hist-tours-list .hist-item')).toContainText('PW-TEST-SHARED');\n  133 |\n  134 |     // Search by the creator's name instead of the title\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:131:63"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:56:26.446Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-0a491-r-including-by-creator-name-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 63,
                        "line": 131
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-31060f1383dd39e5b179",
              "file": "tour_pg_sharing.spec.js",
              "line": 121,
              "column": 3
            },
            {
              "title": "multi-token search: \"BTC himach\" matches a title with both words, not adjacent",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1810,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6179,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    - locator resolved to <div id=\"toast\"></div>\u001b[22m\n\u001b[2m    - unexpected value \"\"\u001b[22m\n\u001b[2m    9 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    - locator resolved to <div id=\"toast\"></div>\u001b[22m\n\u001b[2m    - unexpected value \"\"\u001b[22m\n\u001b[2m    9 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:151:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 151
                        },
                        "snippet": "  149 |     await page.fill('#inp-save-tour-title', 'PW-TEST-Verve-BTC-Batch-Himachal');\n  150 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 151 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  152 |\n  153 |     await page.goto(BASE_URL);\n  154 |     await page.waitForTimeout(300);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 151
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    - locator resolved to <div id=\"toast\"></div>\u001b[22m\n\u001b[2m    - unexpected value \"\"\u001b[22m\n\u001b[2m    9 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  149 |     await page.fill('#inp-save-tour-title', 'PW-TEST-Verve-BTC-Batch-Himachal');\n  150 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 151 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  152 |\n  153 |     await page.goto(BASE_URL);\n  154 |     await page.waitForTimeout(300);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:151:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:56:34.463Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 151
                      }
                    },
                    {
                      "workerIndex": 1811,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6203,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    - locator resolved to <div id=\"toast\"></div>\u001b[22m\n\u001b[2m    - unexpected value \"\"\u001b[22m\n\u001b[2m    9 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    - locator resolved to <div id=\"toast\"></div>\u001b[22m\n\u001b[2m    - unexpected value \"\"\u001b[22m\n\u001b[2m    9 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:151:42",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 42,
                          "line": 151
                        },
                        "snippet": "  149 |     await page.fill('#inp-save-tour-title', 'PW-TEST-Verve-BTC-Batch-Himachal');\n  150 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 151 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  152 |\n  153 |     await page.goto(BASE_URL);\n  154 |     await page.waitForTimeout(300);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 42,
                            "line": 151
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#toast')\nTimeout: 5000ms\n\u001b[32m- Expected substring  - 1\u001b[39m\n\u001b[31m+ Received string     + 2\u001b[39m\n\n\u001b[32m- Tour saved\u001b[39m\n\u001b[31m+ Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[39m\n\u001b[31m+ <html><he\u001b[39m\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#toast')\u001b[22m\n\u001b[2m    - locator resolved to <div id=\"toast\"></div>\u001b[22m\n\u001b[2m    - unexpected value \"\"\u001b[22m\n\u001b[2m    9 × locator resolved to <div id=\"toast\" class=\"show err\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\u001b[2m    4 × locator resolved to <div class=\"\" id=\"toast\">Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC…</div>\u001b[22m\n\u001b[2m      - unexpected value \"Save failed: /db/tours: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\u001b[22m\n\u001b[2m<html><he\"\u001b[22m\n\n\n  149 |     await page.fill('#inp-save-tour-title', 'PW-TEST-Verve-BTC-Batch-Himachal');\n  150 |     await page.click('#modal-save-tour >> text=✓ Save tour');\n> 151 |     await expect(page.locator('#toast')).toContainText('Tour saved');\n      |                                          ^\n  152 |\n  153 |     await page.goto(BASE_URL);\n  154 |     await page.waitForTimeout(300);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:151:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:56:41.746Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-6e670-ith-both-words-not-adjacent-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 42,
                        "line": 151
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-fdc1c88c398042a85f74",
              "file": "tour_pg_sharing.spec.js",
              "line": 145,
              "column": 3
            },
            {
              "title": "multi-token search on Uploads tab: \"BTC himach\" matches a filename with both words",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1812,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7260,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:186:114",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 114,
                          "line": 186
                        },
                        "snippet": "  184 |     await page.fill('#search-uploads', 'BTC himach');\n  185 |     await page.waitForTimeout(200);\n> 186 |     await expect(page.locator('#hist-uploads-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  187 |\n  188 |     await page.fill('#search-uploads', 'BTC nonexistentword');\n  189 |     await page.waitForTimeout(200);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 114,
                            "line": 186
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n\n  184 |     await page.fill('#search-uploads', 'BTC himach');\n  185 |     await page.waitForTimeout(200);\n> 186 |     await expect(page.locator('#hist-uploads-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  187 |\n  188 |     await page.fill('#search-uploads', 'BTC nonexistentword');\n  189 |     await page.waitForTimeout(200);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:186:114"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:56:49.142Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 114,
                        "line": 186
                      }
                    },
                    {
                      "workerIndex": 1813,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7326,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:186:114",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 114,
                          "line": 186
                        },
                        "snippet": "  184 |     await page.fill('#search-uploads', 'BTC himach');\n  185 |     await page.waitForTimeout(200);\n> 186 |     await expect(page.locator('#hist-uploads-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  187 |\n  188 |     await page.fill('#search-uploads', 'BTC nonexistentword');\n  189 |     await page.waitForTimeout(200);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 114,
                            "line": 186
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-uploads-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n\n  184 |     await page.fill('#search-uploads', 'BTC himach');\n  185 |     await page.waitForTimeout(200);\n> 186 |     await expect(page.locator('#hist-uploads-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  187 |\n  188 |     await page.fill('#search-uploads', 'BTC nonexistentword');\n  189 |     await page.waitForTimeout(200);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:186:114"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:56:57.494Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-7b12c--a-filename-with-both-words-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 114,
                        "line": 186
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-20a936ecee63d9812602",
              "file": "tour_pg_sharing.spec.js",
              "line": 171,
              "column": 3
            },
            {
              "title": "multi-token search on Batches tab: \"BTC himach\" matches a batch name with both words",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1814,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7302,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:214:114",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 114,
                          "line": 214
                        },
                        "snippet": "  212 |     await page.fill('#search-batches', 'BTC himach');\n  213 |     await page.waitForTimeout(200);\n> 214 |     await expect(page.locator('#hist-batches-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  215 |\n  216 |     await page.fill('#search-batches', 'BTC nonexistentword');\n  217 |     await page.waitForTimeout(200);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 114,
                            "line": 214
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n\n  212 |     await page.fill('#search-batches', 'BTC himach');\n  213 |     await page.waitForTimeout(200);\n> 214 |     await expect(page.locator('#hist-batches-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  215 |\n  216 |     await page.fill('#search-batches', 'BTC nonexistentword');\n  217 |     await page.waitForTimeout(200);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:214:114"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:06.003Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 114,
                        "line": 214
                      }
                    },
                    {
                      "workerIndex": 1815,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7250,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:214:114",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 114,
                          "line": 214
                        },
                        "snippet": "  212 |     await page.fill('#search-batches', 'BTC himach');\n  213 |     await page.waitForTimeout(200);\n> 214 |     await expect(page.locator('#hist-batches-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  215 |\n  216 |     await page.fill('#search-batches', 'BTC nonexistentword');\n  217 |     await page.waitForTimeout(200);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 114,
                            "line": 214
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-batches-list .hist-item').filter({ hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })\u001b[22m\n\n\n  212 |     await page.fill('#search-batches', 'BTC himach');\n  213 |     await page.waitForTimeout(200);\n> 214 |     await expect(page.locator('#hist-batches-list .hist-item', { hasText: 'PW-TEST-Verve-BTC-Batch-Himachal' })).toBeVisible();\n      |                                                                                                                  ^\n  215 |\n  216 |     await page.fill('#search-batches', 'BTC nonexistentword');\n  217 |     await page.waitForTimeout(200);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:214:114"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:57:14.394Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ba33a--batch-name-with-both-words-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 114,
                        "line": 214
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-e34640d5d092a002151b",
              "file": "tour_pg_sharing.spec.js",
              "line": 193,
              "column": 3
            },
            {
              "title": "User A sees BOTH own tours (shared + private) in their own History, with delete buttons",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1816,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6518,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:231:26",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 26,
                          "line": 231
                        },
                        "snippet": "  229 |     const shared  = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n  230 |     const priv    = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-PRIVATE' });\n> 231 |     await expect(shared).toBeVisible();\n      |                          ^\n  232 |     await expect(priv).toBeVisible();\n  233 |     await expect(shared.locator('.hist-btn.danger')).toHaveCount(1); // owner sees delete\n  234 |     await expect(priv.locator('.hist-btn.danger')).toHaveCount(1);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 26,
                            "line": 231
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n\n  229 |     const shared  = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n  230 |     const priv    = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-PRIVATE' });\n> 231 |     await expect(shared).toBeVisible();\n      |                          ^\n  232 |     await expect(priv).toBeVisible();\n  233 |     await expect(shared.locator('.hist-btn.danger')).toHaveCount(1); // owner sees delete\n  234 |     await expect(priv.locator('.hist-btn.danger')).toHaveCount(1);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:231:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:22.778Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 26,
                        "line": 231
                      }
                    },
                    {
                      "workerIndex": 1817,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6586,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:231:26",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                          "column": 26,
                          "line": 231
                        },
                        "snippet": "  229 |     const shared  = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n  230 |     const priv    = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-PRIVATE' });\n> 231 |     await expect(shared).toBeVisible();\n      |                          ^\n  232 |     await expect(priv).toBeVisible();\n  233 |     await expect(shared.locator('.hist-btn.danger')).toHaveCount(1); // owner sees delete\n  234 |     await expect(priv.locator('.hist-btn.danger')).toHaveCount(1);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                            "column": 26,
                            "line": 231
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#hist-tours-list .hist-item').filter({ hasText: 'PW-TEST-SHARED' })\u001b[22m\n\n\n  229 |     const shared  = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-SHARED' });\n  230 |     const priv    = page.locator('#hist-tours-list .hist-item', { hasText: 'PW-TEST-PRIVATE' });\n> 231 |     await expect(shared).toBeVisible();\n      |                          ^\n  232 |     await expect(priv).toBeVisible();\n  233 |     await expect(shared.locator('.hist-btn.danger')).toHaveCount(1); // owner sees delete\n  234 |     await expect(priv.locator('.hist-btn.danger')).toHaveCount(1);\n    at /var/www/360lm/tests/tour_pg_sharing.spec.js:231:26"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:57:30.385Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg_sharing-Tour-Playg-ebc8e-History-with-delete-buttons-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg_sharing.spec.js",
                        "column": 26,
                        "line": 231
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "7856fccbadda5205b698-b80dc367057119d43910",
              "file": "tour_pg_sharing.spec.js",
              "line": 221,
              "column": 3
            },
            {
              "title": "Global \"keep my saves private\" toggle persists per-user in localStorage",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1818,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1464,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:38.199Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "7856fccbadda5205b698-542c51bdfc5e9ef51586",
              "file": "tour_pg_sharing.spec.js",
              "line": 239,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "tour_pg.spec.js",
      "file": "tour_pg.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Tour Playground — Smoke",
          "file": "tour_pg.spec.js",
          "line": 32,
          "column": 6,
          "specs": [
            {
              "title": "page returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 810,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 583,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:37.894Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-07a7be6c69471b097587",
              "file": "tour_pg.spec.js",
              "line": 33,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 810,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 177,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:38.500Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-3663c6956c03edf85c87",
              "file": "tour_pg.spec.js",
              "line": 38,
              "column": 3
            },
            {
              "title": "sample.xlsx is downloadable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 810,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 12,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:38.697Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-a984cd7f10c08f210a94",
              "file": "tour_pg.spec.js",
              "line": 43,
              "column": 3
            },
            {
              "title": "HTML carries current tour-pg SW version",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 810,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:38.726Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-f8b419d6030eb8f1dfd4",
              "file": "tour_pg.spec.js",
              "line": 52,
              "column": 3
            },
            {
              "title": "proxy /health returns ok",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 810,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 22,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:63:24",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 24,
                          "line": 63
                        },
                        "snippet": "  61 |     const ctx = await request.newContext();\n  62 |     const r   = await ctx.get(`${PROXY}/health`);\n> 63 |     expect(r.status()).toBe(200);\n     |                        ^\n  64 |     const json = await r.json();\n  65 |     expect(json.ok).toBe(true);\n  66 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 24,
                            "line": 63
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  61 |     const ctx = await request.newContext();\n  62 |     const r   = await ctx.get(`${PROXY}/health`);\n> 63 |     expect(r.status()).toBe(200);\n     |                        ^\n  64 |     const json = await r.json();\n  65 |     expect(json.ok).toBe(true);\n  66 |     await ctx.dispose();\n    at /var/www/360lm/tests/tour_pg.spec.js:63:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:38.750Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—-Smoke-proxy-health-returns-ok-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 24,
                        "line": 63
                      }
                    },
                    {
                      "workerIndex": 811,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:63:24",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 24,
                          "line": 63
                        },
                        "snippet": "  61 |     const ctx = await request.newContext();\n  62 |     const r   = await ctx.get(`${PROXY}/health`);\n> 63 |     expect(r.status()).toBe(200);\n     |                        ^\n  64 |     const json = await r.json();\n  65 |     expect(json.ok).toBe(true);\n  66 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 24,
                            "line": 63
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  61 |     const ctx = await request.newContext();\n  62 |     const r   = await ctx.get(`${PROXY}/health`);\n> 63 |     expect(r.status()).toBe(200);\n     |                        ^\n  64 |     const json = await r.json();\n  65 |     expect(json.ok).toBe(true);\n  66 |     await ctx.dispose();\n    at /var/www/360lm/tests/tour_pg.spec.js:63:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:57:39.645Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—-Smoke-proxy-health-returns-ok-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 24,
                        "line": 63
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c6527e00e898397fb4a5-4f4987b0ee72f032f49f",
              "file": "tour_pg.spec.js",
              "line": 60,
              "column": 3
            },
            {
              "title": "page returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1818,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 753,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:40.046Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-9dbe1966c7dd5452b371",
              "file": "tour_pg.spec.js",
              "line": 33,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1818,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 159,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:40.819Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-c7fda584a64f77dd8b37",
              "file": "tour_pg.spec.js",
              "line": 38,
              "column": 3
            },
            {
              "title": "sample.xlsx is downloadable",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1818,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 16,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:40.997Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-8620fb99767dbe91366c",
              "file": "tour_pg.spec.js",
              "line": 43,
              "column": 3
            },
            {
              "title": "HTML carries current tour-pg SW version",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1818,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 15,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:41.026Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-fe54eff155321b030cdd",
              "file": "tour_pg.spec.js",
              "line": 52,
              "column": 3
            },
            {
              "title": "proxy /health returns ok",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1818,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 26,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:63:24",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 24,
                          "line": 63
                        },
                        "snippet": "  61 |     const ctx = await request.newContext();\n  62 |     const r   = await ctx.get(`${PROXY}/health`);\n> 63 |     expect(r.status()).toBe(200);\n     |                        ^\n  64 |     const json = await r.json();\n  65 |     expect(json.ok).toBe(true);\n  66 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 24,
                            "line": 63
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  61 |     const ctx = await request.newContext();\n  62 |     const r   = await ctx.get(`${PROXY}/health`);\n> 63 |     expect(r.status()).toBe(200);\n     |                        ^\n  64 |     const json = await r.json();\n  65 |     expect(json.ok).toBe(true);\n  66 |     await ctx.dispose();\n    at /var/www/360lm/tests/tour_pg.spec.js:63:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:41.052Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—-Smoke-proxy-health-returns-ok-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 24,
                        "line": 63
                      }
                    },
                    {
                      "workerIndex": 1819,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:63:24",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 24,
                          "line": 63
                        },
                        "snippet": "  61 |     const ctx = await request.newContext();\n  62 |     const r   = await ctx.get(`${PROXY}/health`);\n> 63 |     expect(r.status()).toBe(200);\n     |                        ^\n  64 |     const json = await r.json();\n  65 |     expect(json.ok).toBe(true);\n  66 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 24,
                            "line": 63
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  61 |     const ctx = await request.newContext();\n  62 |     const r   = await ctx.get(`${PROXY}/health`);\n> 63 |     expect(r.status()).toBe(200);\n     |                        ^\n  64 |     const json = await r.json();\n  65 |     expect(json.ok).toBe(true);\n  66 |     await ctx.dispose();\n    at /var/www/360lm/tests/tour_pg.spec.js:63:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:57:41.984Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—-Smoke-proxy-health-returns-ok-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 24,
                        "line": 63
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c6527e00e898397fb4a5-c27a1f81f0f1bef71bf0",
              "file": "tour_pg.spec.js",
              "line": 60,
              "column": 3
            }
          ]
        },
        {
          "title": "Tour Playground — HTML structure",
          "file": "tour_pg.spec.js",
          "line": 73,
          "column": 6,
          "specs": [
            {
              "title": "two main screens declared",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 12,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:40.586Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-695c509119fd5d12f8b3",
              "file": "tour_pg.spec.js",
              "line": 81,
              "column": 3
            },
            {
              "title": "upload form widgets present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 7,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:40.753Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-0a8482cda7d5afe32715",
              "file": "tour_pg.spec.js",
              "line": 86,
              "column": 3
            },
            {
              "title": "editor screen widgets present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 28,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:40.793Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-4aeb9f684d0e10040cef",
              "file": "tour_pg.spec.js",
              "line": 94,
              "column": 3
            },
            {
              "title": "share modal present with 5 actions",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:40.847Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-e3ac227106e400cac68e",
              "file": "tour_pg.spec.js",
              "line": 110,
              "column": 3
            },
            {
              "title": "mid-tour replan widget present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:40.906Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-8391bbee5cdba382f02e",
              "file": "tour_pg.spec.js",
              "line": 119,
              "column": 3
            },
            {
              "title": "SortableJS CDN + SheetJS self-hosted",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:40.926Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-bfefb2f7d1e685d72979",
              "file": "tour_pg.spec.js",
              "line": 125,
              "column": 3
            },
            {
              "title": "Leaflet CDN + inline map widget present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:40.945Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-6a6396b44064bf473745",
              "file": "tour_pg.spec.js",
              "line": 130,
              "column": 3
            },
            {
              "title": "manual planner screen + entry button + back/pill present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 6,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:40.963Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-7530a846c74c42250e96",
              "file": "tour_pg.spec.js",
              "line": 140,
              "column": 3
            },
            {
              "title": "shared refactor functions present (S0)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:40.980Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-a327237aba39686d70f3",
              "file": "tour_pg.spec.js",
              "line": 151,
              "column": 3
            },
            {
              "title": "two main screens declared",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 9,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:42.915Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-3efaae47ee3ef653f64c",
              "file": "tour_pg.spec.js",
              "line": 81,
              "column": 3
            },
            {
              "title": "upload form widgets present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 20,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:43.047Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-5c26810bed9cc28fce33",
              "file": "tour_pg.spec.js",
              "line": 86,
              "column": 3
            },
            {
              "title": "editor screen widgets present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:43.090Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-a56bef580f604eff9386",
              "file": "tour_pg.spec.js",
              "line": 94,
              "column": 3
            },
            {
              "title": "share modal present with 5 actions",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:43.130Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-b3d5bcbc5dc2526d303a",
              "file": "tour_pg.spec.js",
              "line": 110,
              "column": 3
            },
            {
              "title": "mid-tour replan widget present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 15,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:43.157Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-6dbbc2f246d66bdb483d",
              "file": "tour_pg.spec.js",
              "line": 119,
              "column": 3
            },
            {
              "title": "SortableJS CDN + SheetJS self-hosted",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:43.186Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-61c3930d8e82775c0d0c",
              "file": "tour_pg.spec.js",
              "line": 125,
              "column": 3
            },
            {
              "title": "Leaflet CDN + inline map widget present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:43.205Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-ac9849b17b955308785e",
              "file": "tour_pg.spec.js",
              "line": 130,
              "column": 3
            },
            {
              "title": "manual planner screen + entry button + back/pill present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 8,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:43.230Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-63441ac4b0f36122a81c",
              "file": "tour_pg.spec.js",
              "line": 140,
              "column": 3
            },
            {
              "title": "shared refactor functions present (S0)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 4,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:43.252Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-3add251077b2f20eea70",
              "file": "tour_pg.spec.js",
              "line": 151,
              "column": 3
            }
          ]
        },
        {
          "title": "Tour Playground — Auth",
          "file": "tour_pg.spec.js",
          "line": 161,
          "column": 6,
          "specs": [
            {
              "title": "no session → redirects to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 727,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:40.995Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-83dd50fbaa77ebc47a5d",
              "file": "tour_pg.spec.js",
              "line": 162,
              "column": 3
            },
            {
              "title": "valid session → upload screen visible",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 903,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:41.858Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-ac58e3c45c229c64a5c8",
              "file": "tour_pg.spec.js",
              "line": 171,
              "column": 3
            },
            {
              "title": "user pill shows logged-in name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 854,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:42.789Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-b105e116251a5a974331",
              "file": "tour_pg.spec.js",
              "line": 180,
              "column": 3
            },
            {
              "title": "no session → redirects to /hub/",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 728,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:43.272Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-8a7b32ab13ba7e650d6d",
              "file": "tour_pg.spec.js",
              "line": 162,
              "column": 3
            },
            {
              "title": "valid session → upload screen visible",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 883,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:44.134Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-c08ca36371ec8fd17b5d",
              "file": "tour_pg.spec.js",
              "line": 171,
              "column": 3
            },
            {
              "title": "user pill shows logged-in name",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 859,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:45.038Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-debfcd19a58dd43fb67b",
              "file": "tour_pg.spec.js",
              "line": 180,
              "column": 3
            }
          ]
        },
        {
          "title": "Tour Playground — Proxy end-to-end",
          "file": "tour_pg.spec.js",
          "line": 191,
          "column": 6,
          "specs": [
            {
              "title": "full optimise + drag-reorder + exclude flow",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 812,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 31,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:208:27",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 27,
                          "line": 208
                        },
                        "snippet": "  206 |     // 1. Optimise\n  207 |     const optR = await ctx.post(`${PROXY}/optimize`, { ...opts, data: JSON.stringify({ origin, stops, roundTrip: true }) });\n> 208 |     expect(optR.status()).toBe(200);\n      |                           ^\n  209 |     const opt = await optR.json();\n  210 |     expect(opt.ok).toBe(true);\n  211 |     expect(Array.isArray(opt.waypoint_order)).toBe(true);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 27,
                            "line": 208
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  206 |     // 1. Optimise\n  207 |     const optR = await ctx.post(`${PROXY}/optimize`, { ...opts, data: JSON.stringify({ origin, stops, roundTrip: true }) });\n> 208 |     expect(optR.status()).toBe(200);\n      |                           ^\n  209 |     const opt = await optR.json();\n  210 |     expect(opt.ok).toBe(true);\n  211 |     expect(Array.isArray(opt.waypoint_order)).toBe(true);\n    at /var/www/360lm/tests/tour_pg.spec.js:208:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:43.667Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0d3d0-e-drag-reorder-exclude-flow-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 27,
                        "line": 208
                      }
                    },
                    {
                      "workerIndex": 813,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:208:27",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 27,
                          "line": 208
                        },
                        "snippet": "  206 |     // 1. Optimise\n  207 |     const optR = await ctx.post(`${PROXY}/optimize`, { ...opts, data: JSON.stringify({ origin, stops, roundTrip: true }) });\n> 208 |     expect(optR.status()).toBe(200);\n      |                           ^\n  209 |     const opt = await optR.json();\n  210 |     expect(opt.ok).toBe(true);\n  211 |     expect(Array.isArray(opt.waypoint_order)).toBe(true);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 27,
                            "line": 208
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  206 |     // 1. Optimise\n  207 |     const optR = await ctx.post(`${PROXY}/optimize`, { ...opts, data: JSON.stringify({ origin, stops, roundTrip: true }) });\n> 208 |     expect(optR.status()).toBe(200);\n      |                           ^\n  209 |     const opt = await optR.json();\n  210 |     expect(opt.ok).toBe(true);\n  211 |     expect(Array.isArray(opt.waypoint_order)).toBe(true);\n    at /var/www/360lm/tests/tour_pg.spec.js:208:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:57:44.558Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0d3d0-e-drag-reorder-exclude-flow-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 27,
                        "line": 208
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c6527e00e898397fb4a5-d9eb7610af698ae1097f",
              "file": "tour_pg.spec.js",
              "line": 192,
              "column": 3
            },
            {
              "title": "matrix + 2-opt path for 14 stops",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 814,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 63,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:255:24",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 24,
                          "line": 255
                        },
                        "snippet": "  253 |       data: JSON.stringify({ origin: '30.7046,76.7178', stops, roundTrip: true }),\n  254 |     });\n> 255 |     expect(r.status()).toBe(200);\n      |                        ^\n  256 |     const j = await r.json();\n  257 |     expect(j.ok).toBe(true);\n  258 |     expect(j.optimisation).toBe('matrix-nn-2opt');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 24,
                            "line": 255
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  253 |       data: JSON.stringify({ origin: '30.7046,76.7178', stops, roundTrip: true }),\n  254 |     });\n> 255 |     expect(r.status()).toBe(200);\n      |                        ^\n  256 |     const j = await r.json();\n  257 |     expect(j.ok).toBe(true);\n  258 |     expect(j.optimisation).toBe('matrix-nn-2opt');\n    at /var/www/360lm/tests/tour_pg.spec.js:255:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:45.470Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--66ee0-rix-2-opt-path-for-14-stops-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 24,
                        "line": 255
                      }
                    },
                    {
                      "workerIndex": 815,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:255:24",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 24,
                          "line": 255
                        },
                        "snippet": "  253 |       data: JSON.stringify({ origin: '30.7046,76.7178', stops, roundTrip: true }),\n  254 |     });\n> 255 |     expect(r.status()).toBe(200);\n      |                        ^\n  256 |     const j = await r.json();\n  257 |     expect(j.ok).toBe(true);\n  258 |     expect(j.optimisation).toBe('matrix-nn-2opt');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 24,
                            "line": 255
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  253 |       data: JSON.stringify({ origin: '30.7046,76.7178', stops, roundTrip: true }),\n  254 |     });\n> 255 |     expect(r.status()).toBe(200);\n      |                        ^\n  256 |     const j = await r.json();\n  257 |     expect(j.ok).toBe(true);\n  258 |     expect(j.optimisation).toBe('matrix-nn-2opt');\n    at /var/www/360lm/tests/tour_pg.spec.js:255:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:57:46.357Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--66ee0-rix-2-opt-path-for-14-stops-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 24,
                        "line": 255
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c6527e00e898397fb4a5-ae600ef94ad0ea6686b6",
              "file": "tour_pg.spec.js",
              "line": 239,
              "column": 3
            },
            {
              "title": "full optimise + drag-reorder + exclude flow",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1820,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 25,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:208:27",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 27,
                          "line": 208
                        },
                        "snippet": "  206 |     // 1. Optimise\n  207 |     const optR = await ctx.post(`${PROXY}/optimize`, { ...opts, data: JSON.stringify({ origin, stops, roundTrip: true }) });\n> 208 |     expect(optR.status()).toBe(200);\n      |                           ^\n  209 |     const opt = await optR.json();\n  210 |     expect(opt.ok).toBe(true);\n  211 |     expect(Array.isArray(opt.waypoint_order)).toBe(true);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 27,
                            "line": 208
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  206 |     // 1. Optimise\n  207 |     const optR = await ctx.post(`${PROXY}/optimize`, { ...opts, data: JSON.stringify({ origin, stops, roundTrip: true }) });\n> 208 |     expect(optR.status()).toBe(200);\n      |                           ^\n  209 |     const opt = await optR.json();\n  210 |     expect(opt.ok).toBe(true);\n  211 |     expect(Array.isArray(opt.waypoint_order)).toBe(true);\n    at /var/www/360lm/tests/tour_pg.spec.js:208:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:45.935Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0d3d0-e-drag-reorder-exclude-flow-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 27,
                        "line": 208
                      }
                    },
                    {
                      "workerIndex": 1821,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:208:27",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 27,
                          "line": 208
                        },
                        "snippet": "  206 |     // 1. Optimise\n  207 |     const optR = await ctx.post(`${PROXY}/optimize`, { ...opts, data: JSON.stringify({ origin, stops, roundTrip: true }) });\n> 208 |     expect(optR.status()).toBe(200);\n      |                           ^\n  209 |     const opt = await optR.json();\n  210 |     expect(opt.ok).toBe(true);\n  211 |     expect(Array.isArray(opt.waypoint_order)).toBe(true);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 27,
                            "line": 208
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  206 |     // 1. Optimise\n  207 |     const optR = await ctx.post(`${PROXY}/optimize`, { ...opts, data: JSON.stringify({ origin, stops, roundTrip: true }) });\n> 208 |     expect(optR.status()).toBe(200);\n      |                           ^\n  209 |     const opt = await optR.json();\n  210 |     expect(opt.ok).toBe(true);\n  211 |     expect(Array.isArray(opt.waypoint_order)).toBe(true);\n    at /var/www/360lm/tests/tour_pg.spec.js:208:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:57:46.787Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0d3d0-e-drag-reorder-exclude-flow-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 27,
                        "line": 208
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c6527e00e898397fb4a5-803599e44c1e6f67a1c1",
              "file": "tour_pg.spec.js",
              "line": 192,
              "column": 3
            },
            {
              "title": "matrix + 2-opt path for 14 stops",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1822,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:255:24",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 24,
                          "line": 255
                        },
                        "snippet": "  253 |       data: JSON.stringify({ origin: '30.7046,76.7178', stops, roundTrip: true }),\n  254 |     });\n> 255 |     expect(r.status()).toBe(200);\n      |                        ^\n  256 |     const j = await r.json();\n  257 |     expect(j.ok).toBe(true);\n  258 |     expect(j.optimisation).toBe('matrix-nn-2opt');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 24,
                            "line": 255
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  253 |       data: JSON.stringify({ origin: '30.7046,76.7178', stops, roundTrip: true }),\n  254 |     });\n> 255 |     expect(r.status()).toBe(200);\n      |                        ^\n  256 |     const j = await r.json();\n  257 |     expect(j.ok).toBe(true);\n  258 |     expect(j.optimisation).toBe('matrix-nn-2opt');\n    at /var/www/360lm/tests/tour_pg.spec.js:255:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:47.677Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--66ee0-rix-2-opt-path-for-14-stops-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 24,
                        "line": 255
                      }
                    },
                    {
                      "workerIndex": 1823,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:255:24",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 24,
                          "line": 255
                        },
                        "snippet": "  253 |       data: JSON.stringify({ origin: '30.7046,76.7178', stops, roundTrip: true }),\n  254 |     });\n> 255 |     expect(r.status()).toBe(200);\n      |                        ^\n  256 |     const j = await r.json();\n  257 |     expect(j.ok).toBe(true);\n  258 |     expect(j.optimisation).toBe('matrix-nn-2opt');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 24,
                            "line": 255
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  253 |       data: JSON.stringify({ origin: '30.7046,76.7178', stops, roundTrip: true }),\n  254 |     });\n> 255 |     expect(r.status()).toBe(200);\n      |                        ^\n  256 |     const j = await r.json();\n  257 |     expect(j.ok).toBe(true);\n  258 |     expect(j.optimisation).toBe('matrix-nn-2opt');\n    at /var/www/360lm/tests/tour_pg.spec.js:255:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:57:48.664Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--66ee0-rix-2-opt-path-for-14-stops-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 24,
                        "line": 255
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c6527e00e898397fb4a5-68347481273a6865f1ad",
              "file": "tour_pg.spec.js",
              "line": 239,
              "column": 3
            }
          ]
        },
        {
          "title": "Tour Playground — Browser JS state",
          "file": "tour_pg.spec.js",
          "line": 268,
          "column": 6,
          "specs": [
            {
              "title": "clicking Go without Excel shows error toast",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 816,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 989,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:47.260Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-1ad6d74924fbe20b2a74",
              "file": "tour_pg.spec.js",
              "line": 269,
              "column": 3
            },
            {
              "title": "round-trip toggle hides/shows destination input",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 816,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1209,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:48.416Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-d37ddfb5b63aa657d1cf",
              "file": "tour_pg.spec.js",
              "line": 277,
              "column": 3
            },
            {
              "title": "full state pipeline via page.evaluate (mimics Excel upload)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 816,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 935,
                      "error": {
                        "message": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30",
                        "stack": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at /var/www/360lm/tests/tour_pg.spec.js:296:31",
                        "location": {
                          "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                          "column": 13,
                          "line": 917
                        }
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                            "column": 13,
                            "line": 917
                          },
                          "message": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at /var/www/360lm/tests/tour_pg.spec.js:296:31"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:49.655Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--418e6-aluate-mimics-Excel-upload--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--418e6-aluate-mimics-Excel-upload--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                        "column": 13,
                        "line": 917
                      }
                    },
                    {
                      "workerIndex": 817,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 1028,
                      "error": {
                        "message": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30",
                        "stack": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at /var/www/360lm/tests/tour_pg.spec.js:296:31",
                        "location": {
                          "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                          "column": 13,
                          "line": 917
                        }
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                            "column": 13,
                            "line": 917
                          },
                          "message": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at /var/www/360lm/tests/tour_pg.spec.js:296:31"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:57:51.510Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--418e6-aluate-mimics-Excel-upload--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--418e6-aluate-mimics-Excel-upload--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                        "column": 13,
                        "line": 917
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c6527e00e898397fb4a5-76a529476034b7654e66",
              "file": "tour_pg.spec.js",
              "line": 290,
              "column": 3
            },
            {
              "title": "manual planner: enter → add stops via factory → Find → editor",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 818,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 1184,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:356:23",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 23,
                          "line": 356
                        },
                        "snippet": "  354 |\n  355 |     expect(result.findDisabledBefore).toBe(false);   // ≥1 stop + origin → enabled\n> 356 |     expect(result.ok).toBe(true);\n      |                       ^\n  357 |     expect(result.n).toBe(3);\n  358 |     expect(result.shapeOK).toBe(true);               // manual stops are editor-shaped\n  359 |     await expect(page.locator('#s-editor')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 23,
                            "line": 356
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n  354 |\n  355 |     expect(result.findDisabledBefore).toBe(false);   // ≥1 stop + origin → enabled\n> 356 |     expect(result.ok).toBe(true);\n      |                       ^\n  357 |     expect(result.n).toBe(3);\n  358 |     expect(result.shapeOK).toBe(true);               // manual stops are editor-shaped\n  359 |     await expect(page.locator('#s-editor')).toBeVisible();\n    at /var/www/360lm/tests/tour_pg.spec.js:356:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:53.572Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0bac1-via-factory-→-Find-→-editor-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0bac1-via-factory-→-Find-→-editor-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 23,
                        "line": 356
                      }
                    },
                    {
                      "workerIndex": 819,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 1187,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:356:23",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 23,
                          "line": 356
                        },
                        "snippet": "  354 |\n  355 |     expect(result.findDisabledBefore).toBe(false);   // ≥1 stop + origin → enabled\n> 356 |     expect(result.ok).toBe(true);\n      |                       ^\n  357 |     expect(result.n).toBe(3);\n  358 |     expect(result.shapeOK).toBe(true);               // manual stops are editor-shaped\n  359 |     await expect(page.locator('#s-editor')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 23,
                            "line": 356
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n  354 |\n  355 |     expect(result.findDisabledBefore).toBe(false);   // ≥1 stop + origin → enabled\n> 356 |     expect(result.ok).toBe(true);\n      |                       ^\n  357 |     expect(result.n).toBe(3);\n  358 |     expect(result.shapeOK).toBe(true);               // manual stops are editor-shaped\n  359 |     await expect(page.locator('#s-editor')).toBeVisible();\n    at /var/www/360lm/tests/tour_pg.spec.js:356:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:57:55.780Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0bac1-via-factory-→-Find-→-editor-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0bac1-via-factory-→-Find-→-editor-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 23,
                        "line": 356
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c6527e00e898397fb4a5-4969201a960fbf508039",
              "file": "tour_pg.spec.js",
              "line": 326,
              "column": 3
            },
            {
              "title": "toggle include then verify state.stops still has the item",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1415,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:58.034Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-fb5425d4cf28fdd89fa0",
              "file": "tour_pg.spec.js",
              "line": 363,
              "column": 3
            },
            {
              "title": "clicking Go without Excel shows error toast",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1824,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1012,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:49.592Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-b55f22712ed011390d18",
              "file": "tour_pg.spec.js",
              "line": 269,
              "column": 3
            },
            {
              "title": "round-trip toggle hides/shows destination input",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1824,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1318,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:50.751Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-2542672225a60b02d394",
              "file": "tour_pg.spec.js",
              "line": 277,
              "column": 3
            },
            {
              "title": "full state pipeline via page.evaluate (mimics Excel upload)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1824,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 1001,
                      "error": {
                        "message": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30",
                        "stack": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at /var/www/360lm/tests/tour_pg.spec.js:296:31",
                        "location": {
                          "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                          "column": 13,
                          "line": 917
                        }
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                            "column": 13,
                            "line": 917
                          },
                          "message": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at /var/www/360lm/tests/tour_pg.spec.js:296:31"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:52.093Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--418e6-aluate-mimics-Excel-upload--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--418e6-aluate-mimics-Excel-upload--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                        "column": 13,
                        "line": 917
                      }
                    },
                    {
                      "workerIndex": 1825,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 1042,
                      "error": {
                        "message": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30",
                        "stack": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at /var/www/360lm/tests/tour_pg.spec.js:296:31",
                        "location": {
                          "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                          "column": 13,
                          "line": 917
                        }
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                            "column": 13,
                            "line": 917
                          },
                          "message": "Error: page.evaluate: Error: /optimize: 404 <!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">\n<html><he\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at api (https://dev.srv1111289.hstgr.cloud/tour-pg/:917:13)\n    at async eval (eval at evaluate (:302:30), <anonymous>:60:19)\n    at async <anonymous>:328:30\n    at /var/www/360lm/tests/tour_pg.spec.js:296:31"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:57:53.948Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--418e6-aluate-mimics-Excel-upload--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--418e6-aluate-mimics-Excel-upload--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "https://dev.srv1111289.hstgr.cloud/tour-pg/",
                        "column": 13,
                        "line": 917
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c6527e00e898397fb4a5-ee875d59fbce16fa3a8f",
              "file": "tour_pg.spec.js",
              "line": 290,
              "column": 3
            },
            {
              "title": "manual planner: enter → add stops via factory → Find → editor",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1826,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 1192,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:356:23",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 23,
                          "line": 356
                        },
                        "snippet": "  354 |\n  355 |     expect(result.findDisabledBefore).toBe(false);   // ≥1 stop + origin → enabled\n> 356 |     expect(result.ok).toBe(true);\n      |                       ^\n  357 |     expect(result.n).toBe(3);\n  358 |     expect(result.shapeOK).toBe(true);               // manual stops are editor-shaped\n  359 |     await expect(page.locator('#s-editor')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 23,
                            "line": 356
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n  354 |\n  355 |     expect(result.findDisabledBefore).toBe(false);   // ≥1 stop + origin → enabled\n> 356 |     expect(result.ok).toBe(true);\n      |                       ^\n  357 |     expect(result.n).toBe(3);\n  358 |     expect(result.shapeOK).toBe(true);               // manual stops are editor-shaped\n  359 |     await expect(page.locator('#s-editor')).toBeVisible();\n    at /var/www/360lm/tests/tour_pg.spec.js:356:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:57:56.047Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0bac1-via-factory-→-Find-→-editor-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0bac1-via-factory-→-Find-→-editor-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 23,
                        "line": 356
                      }
                    },
                    {
                      "workerIndex": 1827,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 1251,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n    at /var/www/360lm/tests/tour_pg.spec.js:356:23",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_pg.spec.js",
                          "column": 23,
                          "line": 356
                        },
                        "snippet": "  354 |\n  355 |     expect(result.findDisabledBefore).toBe(false);   // ≥1 stop + origin → enabled\n> 356 |     expect(result.ok).toBe(true);\n      |                       ^\n  357 |     expect(result.n).toBe(3);\n  358 |     expect(result.shapeOK).toBe(true);               // manual stops are editor-shaped\n  359 |     await expect(page.locator('#s-editor')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_pg.spec.js",
                            "column": 23,
                            "line": 356
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32mtrue\u001b[39m\nReceived: \u001b[31mfalse\u001b[39m\n\n  354 |\n  355 |     expect(result.findDisabledBefore).toBe(false);   // ≥1 stop + origin → enabled\n> 356 |     expect(result.ok).toBe(true);\n      |                       ^\n  357 |     expect(result.n).toBe(3);\n  358 |     expect(result.shapeOK).toBe(true);               // manual stops are editor-shaped\n  359 |     await expect(page.locator('#s-editor')).toBeVisible();\n    at /var/www/360lm/tests/tour_pg.spec.js:356:23"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:57:58.287Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0bac1-via-factory-→-Find-→-editor-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_pg-Tour-Playground-—--0bac1-via-factory-→-Find-→-editor-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_pg.spec.js",
                        "column": 23,
                        "line": 356
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "c6527e00e898397fb4a5-3eed26b6c0e35f36d7c9",
              "file": "tour_pg.spec.js",
              "line": 326,
              "column": 3
            },
            {
              "title": "toggle include then verify state.stops still has the item",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1828,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 3493,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:00.677Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-ee0ea69eaa8d3fc3f0d7",
              "file": "tour_pg.spec.js",
              "line": 363,
              "column": 3
            }
          ]
        },
        {
          "title": "Tour Playground — Hub",
          "file": "tour_pg.spec.js",
          "line": 395,
          "column": 6,
          "specs": [
            {
              "title": "PWA is registered in hub.pwa_registry with grants",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 103,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:59.656Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-dbd8d78ec13d6ad2657d",
              "file": "tour_pg.spec.js",
              "line": 396,
              "column": 3
            },
            {
              "title": "PWA is registered in hub.pwa_registry with grants",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1828,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 174,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:04.507Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-8827f07514089a554a45",
              "file": "tour_pg.spec.js",
              "line": 396,
              "column": 3
            }
          ]
        },
        {
          "title": "Tour Playground — Excel header-row detection",
          "file": "tour_pg.spec.js",
          "line": 422,
          "column": 6,
          "specs": [
            {
              "title": "findHeaderRowIndex skips title rows and finds real headers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1136,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:57:59.773Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-dc2e3161aaff41137354",
              "file": "tour_pg.spec.js",
              "line": 423,
              "column": 3
            },
            {
              "title": "findHeaderRowIndex returns 0 for well-formed files (no title rows)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1359,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:00.936Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-bad23d34ad3665a04928",
              "file": "tour_pg.spec.js",
              "line": 442,
              "column": 3
            },
            {
              "title": "source contains findHeaderRowIndex function",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 25,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:02.344Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-0d8675d8c0a33d8b0f92",
              "file": "tour_pg.spec.js",
              "line": 457,
              "column": 3
            },
            {
              "title": "cache version is v22 or later",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 25,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:02.405Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-dbe9d43d0bf07ff3bb80",
              "file": "tour_pg.spec.js",
              "line": 465,
              "column": 3
            },
            {
              "title": "findHeaderRowIndex skips title rows and finds real headers",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1828,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2933,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:04.708Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-469f75b1ee2b672dcf48",
              "file": "tour_pg.spec.js",
              "line": 423,
              "column": 3
            },
            {
              "title": "findHeaderRowIndex returns 0 for well-formed files (no title rows)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1828,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 2105,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:07.709Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-ed54ef0227153f9ae35e",
              "file": "tour_pg.spec.js",
              "line": 442,
              "column": 3
            },
            {
              "title": "source contains findHeaderRowIndex function",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1828,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 29,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:09.862Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-4f10869e881490f3f873",
              "file": "tour_pg.spec.js",
              "line": 457,
              "column": 3
            },
            {
              "title": "cache version is v22 or later",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1828,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 19,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:09.915Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "c6527e00e898397fb4a5-f86894a2e417ced9e5ee",
              "file": "tour_pg.spec.js",
              "line": 465,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "tour_planner.spec.js",
      "file": "tour_planner.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Tour Planner — Smoke",
          "file": "tour_planner.spec.js",
          "line": 9,
          "column": 6,
          "specs": [
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 820,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1098,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:02.470Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-8c53e0eabf82dbe7e62c",
              "file": "tour_planner.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 820,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 235,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/tour_planner.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:03.616Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 821,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 370,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/tour_planner.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:58:04.864Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-c7bdb1a1798547e85324",
              "file": "tour_planner.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 822,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 392,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360tourplan-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360tourplan-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/tour_planner.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:06.465Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-sw-js-is-versioned-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-sw-js-is-versioned-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 823,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 324,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360tourplan-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360tourplan-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/tour_planner.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:58:07.892Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-sw-js-is-versioned-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-sw-js-is-versioned-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-8547407c9584a4894db5",
              "file": "tour_planner.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1828,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1050,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:09.976Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-f86b5917f06ae3065859",
              "file": "tour_planner.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1828,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 218,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/tour_planner.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:11.057Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 1829,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 510,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is versioned', async ({ page }) => {\n    at /var/www/360lm/tests/tour_planner.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:58:12.328Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-713d88f00af660738c12",
              "file": "tour_planner.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is versioned",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1830,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 328,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360tourplan-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360tourplan-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/tour_planner.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:14.064Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-sw-js-is-versioned-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-sw-js-is-versioned-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 1831,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 421,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360tourplan-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/360tourplan-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toMatch(/360tourplan-v\\d+/);\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/tour_planner.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:58:15.719Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-sw-js-is-versioned-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner-—-Smoke-sw-js-is-versioned-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-ac90e1fe603e5757cc3e",
              "file": "tour_planner.spec.js",
              "line": 22,
              "column": 3
            }
          ]
        },
        {
          "title": "Tour Planner — HTML Structure",
          "file": "tour_planner.spec.js",
          "line": 29,
          "column": 6,
          "specs": [
            {
              "title": "all screens present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 824,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-login\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-camps\"');\n  42 |     expect(html).toContain('id=\"screen-audit\"');\n  43 |     expect(html).toContain('id=\"screen-manpower\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-login\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-camps\"');\n  42 |     expect(html).toContain('id=\"screen-audit\"');\n  43 |     expect(html).toContain('id=\"screen-manpower\"');\n    at /var/www/360lm/tests/tour_planner.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:09.188Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--a1bcf-ructure-all-screens-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    },
                    {
                      "workerIndex": 825,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-login\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-camps\"');\n  42 |     expect(html).toContain('id=\"screen-audit\"');\n  43 |     expect(html).toContain('id=\"screen-manpower\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-login\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-camps\"');\n  42 |     expect(html).toContain('id=\"screen-audit\"');\n  43 |     expect(html).toContain('id=\"screen-manpower\"');\n    at /var/www/360lm/tests/tour_planner.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:58:10.088Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--a1bcf-ructure-all-screens-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-66fdd530992988e768f5",
              "file": "tour_planner.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub navigation present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 826,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('hub navigation present', () => {\n> 50 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  51 |     expect(html).toContain('🏡');\n  52 |   });\n  53 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('hub navigation present', () => {\n> 50 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  51 |     expect(html).toContain('🏡');\n  52 |   });\n  53 |\n    at /var/www/360lm/tests/tour_planner.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:10.943Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--a442c-ture-hub-navigation-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    },
                    {
                      "workerIndex": 827,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('hub navigation present', () => {\n> 50 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  51 |     expect(html).toContain('🏡');\n  52 |   });\n  53 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('hub navigation present', () => {\n> 50 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  51 |     expect(html).toContain('🏡');\n  52 |   });\n  53 |\n    at /var/www/360lm/tests/tour_planner.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:58:11.882Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--a442c-ture-hub-navigation-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-eed061ea0b29073ffbc8",
              "file": "tour_planner.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "map and route elements present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 828,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:55:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 55
                        },
                        "snippet": "  53 |\n  54 |   test('map and route elements present', () => {\n> 55 |     expect(html).toContain('id=\"gmap\"');\n     |                  ^\n  56 |     expect(html).toContain('id=\"route-content\"');\n  57 |     expect(html).toContain('id=\"map-wrap\"');\n  58 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 55
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  53 |\n  54 |   test('map and route elements present', () => {\n> 55 |     expect(html).toContain('id=\"gmap\"');\n     |                  ^\n  56 |     expect(html).toContain('id=\"route-content\"');\n  57 |     expect(html).toContain('id=\"map-wrap\"');\n  58 |   });\n    at /var/www/360lm/tests/tour_planner.spec.js:55:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:12.786Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--685b4--and-route-elements-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 55
                      }
                    },
                    {
                      "workerIndex": 829,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:55:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 55
                        },
                        "snippet": "  53 |\n  54 |   test('map and route elements present', () => {\n> 55 |     expect(html).toContain('id=\"gmap\"');\n     |                  ^\n  56 |     expect(html).toContain('id=\"route-content\"');\n  57 |     expect(html).toContain('id=\"map-wrap\"');\n  58 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 55
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  53 |\n  54 |   test('map and route elements present', () => {\n> 55 |     expect(html).toContain('id=\"gmap\"');\n     |                  ^\n  56 |     expect(html).toContain('id=\"route-content\"');\n  57 |     expect(html).toContain('id=\"map-wrap\"');\n  58 |   });\n    at /var/www/360lm/tests/tour_planner.spec.js:55:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:58:13.695Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--685b4--and-route-elements-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 55
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-76fa0c610e80893d707e",
              "file": "tour_planner.spec.js",
              "line": 54,
              "column": 3
            },
            {
              "title": "all screens present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1832,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 15,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-login\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-camps\"');\n  42 |     expect(html).toContain('id=\"screen-audit\"');\n  43 |     expect(html).toContain('id=\"screen-manpower\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-login\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-camps\"');\n  42 |     expect(html).toContain('id=\"screen-audit\"');\n  43 |     expect(html).toContain('id=\"screen-manpower\"');\n    at /var/www/360lm/tests/tour_planner.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:17.466Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--a1bcf-ructure-all-screens-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    },
                    {
                      "workerIndex": 1833,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-login\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-camps\"');\n  42 |     expect(html).toContain('id=\"screen-audit\"');\n  43 |     expect(html).toContain('id=\"screen-manpower\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-login\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-login\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-camps\"');\n  42 |     expect(html).toContain('id=\"screen-audit\"');\n  43 |     expect(html).toContain('id=\"screen-manpower\"');\n    at /var/www/360lm/tests/tour_planner.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:58:18.500Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--a1bcf-ructure-all-screens-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-e41c4b8e4d2a479ec2de",
              "file": "tour_planner.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub navigation present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1834,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 12,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('hub navigation present', () => {\n> 50 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  51 |     expect(html).toContain('🏡');\n  52 |   });\n  53 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('hub navigation present', () => {\n> 50 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  51 |     expect(html).toContain('🏡');\n  52 |   });\n  53 |\n    at /var/www/360lm/tests/tour_planner.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:19.418Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--a442c-ture-hub-navigation-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    },
                    {
                      "workerIndex": 1835,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 32,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:50:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 50
                        },
                        "snippet": "  48 |\n  49 |   test('hub navigation present', () => {\n> 50 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  51 |     expect(html).toContain('🏡');\n  52 |   });\n  53 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 50
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"window.location.href='/hub/'\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  48 |\n  49 |   test('hub navigation present', () => {\n> 50 |     expect(html).toContain(\"window.location.href='/hub/'\");\n     |                  ^\n  51 |     expect(html).toContain('🏡');\n  52 |   });\n  53 |\n    at /var/www/360lm/tests/tour_planner.spec.js:50:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:58:20.489Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--a442c-ture-hub-navigation-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 50
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-d3f59c4074d88c38ee97",
              "file": "tour_planner.spec.js",
              "line": 49,
              "column": 3
            },
            {
              "title": "map and route elements present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1836,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:55:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 55
                        },
                        "snippet": "  53 |\n  54 |   test('map and route elements present', () => {\n> 55 |     expect(html).toContain('id=\"gmap\"');\n     |                  ^\n  56 |     expect(html).toContain('id=\"route-content\"');\n  57 |     expect(html).toContain('id=\"map-wrap\"');\n  58 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 55
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  53 |\n  54 |   test('map and route elements present', () => {\n> 55 |     expect(html).toContain('id=\"gmap\"');\n     |                  ^\n  56 |     expect(html).toContain('id=\"route-content\"');\n  57 |     expect(html).toContain('id=\"map-wrap\"');\n  58 |   });\n    at /var/www/360lm/tests/tour_planner.spec.js:55:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:21.607Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--685b4--and-route-elements-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 55
                      }
                    },
                    {
                      "workerIndex": 1837,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 12,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/tour_planner.spec.js:55:18",
                        "location": {
                          "file": "/var/www/360lm/tests/tour_planner.spec.js",
                          "column": 18,
                          "line": 55
                        },
                        "snippet": "  53 |\n  54 |   test('map and route elements present', () => {\n> 55 |     expect(html).toContain('id=\"gmap\"');\n     |                  ^\n  56 |     expect(html).toContain('id=\"route-content\"');\n  57 |     expect(html).toContain('id=\"map-wrap\"');\n  58 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/tour_planner.spec.js",
                            "column": 18,
                            "line": 55
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"gmap\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  53 |\n  54 |   test('map and route elements present', () => {\n> 55 |     expect(html).toContain('id=\"gmap\"');\n     |                  ^\n  56 |     expect(html).toContain('id=\"route-content\"');\n  57 |     expect(html).toContain('id=\"map-wrap\"');\n  58 |   });\n    at /var/www/360lm/tests/tour_planner.spec.js:55:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:58:22.687Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/tour_planner-Tour-Planner--685b4--and-route-elements-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/tour_planner.spec.js",
                        "column": 18,
                        "line": 55
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "f70a0e9ab0dd7fa6dce0-4a22748780d5acc6e51e",
              "file": "tour_planner.spec.js",
              "line": 54,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "upi_pay.spec.js",
      "file": "upi_pay.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Auth Gate",
          "file": "upi_pay.spec.js",
          "line": 31,
          "column": 6,
          "specs": [
            {
              "title": "no session → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 830,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8267,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/upi_pay.spec.js:34:16",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 16,
                          "line": 34
                        },
                        "snippet": "  32 |   test('no session → redirects to /hub/', async ({ page }) => {\n  33 |     await page.goto(BASE_URL);\n> 34 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  35 |     expect(page.url()).toContain('/hub/');\n  36 |   });\n  37 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 16,
                            "line": 34
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  32 |   test('no session → redirects to /hub/', async ({ page }) => {\n  33 |     await page.goto(BASE_URL);\n> 34 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  35 |     expect(page.url()).toContain('/hub/');\n  36 |   });\n  37 |\n    at /var/www/360lm/tests/upi_pay.spec.js:34:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:14.624Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-no-session-→-redirects-to-hub--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-no-session-→-redirects-to-hub--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 16,
                        "line": 34
                      }
                    },
                    {
                      "workerIndex": 831,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8289,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/upi_pay.spec.js:34:16",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 16,
                          "line": 34
                        },
                        "snippet": "  32 |   test('no session → redirects to /hub/', async ({ page }) => {\n  33 |     await page.goto(BASE_URL);\n> 34 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  35 |     expect(page.url()).toContain('/hub/');\n  36 |   });\n  37 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 16,
                            "line": 34
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  32 |   test('no session → redirects to /hub/', async ({ page }) => {\n  33 |     await page.goto(BASE_URL);\n> 34 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  35 |     expect(page.url()).toContain('/hub/');\n  36 |   });\n  37 |\n    at /var/www/360lm/tests/upi_pay.spec.js:34:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:58:23.903Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-no-session-→-redirects-to-hub--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-no-session-→-redirects-to-hub--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 16,
                        "line": 34
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-185f019f14147fe39d46",
              "file": "upi_pay.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "expired session → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 832,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8263,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/upi_pay.spec.js:42:16",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 16,
                          "line": 42
                        },
                        "snippet": "  40 |     await injectSession(page, expired);\n  41 |     await page.goto(BASE_URL);\n> 42 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  43 |     expect(page.url()).toContain('/hub/');\n  44 |   });\n  45 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 16,
                            "line": 42
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  40 |     await injectSession(page, expired);\n  41 |     await page.goto(BASE_URL);\n> 42 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  43 |     expect(page.url()).toContain('/hub/');\n  44 |   });\n  45 |\n    at /var/www/360lm/tests/upi_pay.spec.js:42:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:33.142Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-expired-session-→-redirects-to-hub--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-expired-session-→-redirects-to-hub--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 16,
                        "line": 42
                      }
                    },
                    {
                      "workerIndex": 833,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8247,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/upi_pay.spec.js:42:16",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 16,
                          "line": 42
                        },
                        "snippet": "  40 |     await injectSession(page, expired);\n  41 |     await page.goto(BASE_URL);\n> 42 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  43 |     expect(page.url()).toContain('/hub/');\n  44 |   });\n  45 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 16,
                            "line": 42
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  40 |     await injectSession(page, expired);\n  41 |     await page.goto(BASE_URL);\n> 42 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  43 |     expect(page.url()).toContain('/hub/');\n  44 |   });\n  45 |\n    at /var/www/360lm/tests/upi_pay.spec.js:42:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:58:42.421Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-expired-session-→-redirects-to-hub--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-expired-session-→-redirects-to-hub--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 16,
                        "line": 42
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-fd57cb2a5782ab25c957",
              "file": "upi_pay.spec.js",
              "line": 38,
              "column": 3
            },
            {
              "title": "non-custodian employee → Access Restricted screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 834,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5233,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:49:58",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 58,
                          "line": 49
                        },
                        "snippet": "  47 |     await injectSession(page, SESSION.mukesh);\n  48 |     await page.goto(BASE_URL);\n> 49 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 5000 });\n     |                                                          ^\n  50 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  51 |   });\n  52 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 58,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  47 |     await injectSession(page, SESSION.mukesh);\n  48 |     await page.goto(BASE_URL);\n> 49 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 5000 });\n     |                                                          ^\n  50 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  51 |   });\n  52 |\n    at /var/www/360lm/tests/upi_pay.spec.js:49:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:58:51.631Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-non-cust-3cd81--→-Access-Restricted-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-non-cust-3cd81--→-Access-Restricted-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 58,
                        "line": 49
                      }
                    },
                    {
                      "workerIndex": 835,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5271,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:49:58",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 58,
                          "line": 49
                        },
                        "snippet": "  47 |     await injectSession(page, SESSION.mukesh);\n  48 |     await page.goto(BASE_URL);\n> 49 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 5000 });\n     |                                                          ^\n  50 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  51 |   });\n  52 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 58,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  47 |     await injectSession(page, SESSION.mukesh);\n  48 |     await page.goto(BASE_URL);\n> 49 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 5000 });\n     |                                                          ^\n  50 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  51 |   });\n  52 |\n    at /var/www/360lm/tests/upi_pay.spec.js:49:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:58:57.795Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-non-cust-3cd81--→-Access-Restricted-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-non-cust-3cd81--→-Access-Restricted-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 58,
                        "line": 49
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-d353d28a00d2d3b521a0",
              "file": "upi_pay.spec.js",
              "line": 46,
              "column": 3
            },
            {
              "title": "custodian (pramod) → dashboard loads",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 836,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6291,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:56:54",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 54,
                          "line": 56
                        },
                        "snippet": "  54 |     await injectSession(page, SESSION.pramod);\n  55 |     await page.goto(BASE_URL);\n> 56 |     await expect(page.locator('#s-dash .hdr-title')).toContainText('UPI Pay', { timeout: 6000 });\n     |                                                      ^\n  57 |     await expect(page.locator('.btn-cta')).toBeVisible();\n  58 |   });\n  59 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 54,
                            "line": 56
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n\n\n  54 |     await injectSession(page, SESSION.pramod);\n  55 |     await page.goto(BASE_URL);\n> 56 |     await expect(page.locator('#s-dash .hdr-title')).toContainText('UPI Pay', { timeout: 6000 });\n     |                                                      ^\n  57 |     await expect(page.locator('.btn-cta')).toBeVisible();\n  58 |   });\n  59 | });\n    at /var/www/360lm/tests/upi_pay.spec.js:56:54"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:59:04.398Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-custodian-pramod-→-dashboard-loads-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-custodian-pramod-→-dashboard-loads-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 54,
                        "line": 56
                      }
                    },
                    {
                      "workerIndex": 837,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:56:54",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 54,
                          "line": 56
                        },
                        "snippet": "  54 |     await injectSession(page, SESSION.pramod);\n  55 |     await page.goto(BASE_URL);\n> 56 |     await expect(page.locator('#s-dash .hdr-title')).toContainText('UPI Pay', { timeout: 6000 });\n     |                                                      ^\n  57 |     await expect(page.locator('.btn-cta')).toBeVisible();\n  58 |   });\n  59 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 54,
                            "line": 56
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n\n\n  54 |     await injectSession(page, SESSION.pramod);\n  55 |     await page.goto(BASE_URL);\n> 56 |     await expect(page.locator('#s-dash .hdr-title')).toContainText('UPI Pay', { timeout: 6000 });\n     |                                                      ^\n  57 |     await expect(page.locator('.btn-cta')).toBeVisible();\n  58 |   });\n  59 | });\n    at /var/www/360lm/tests/upi_pay.spec.js:56:54"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:59:11.717Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-custodian-pramod-→-dashboard-loads-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-custodian-pramod-→-dashboard-loads-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 54,
                        "line": 56
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-128a7c86696b8b3cc319",
              "file": "upi_pay.spec.js",
              "line": 53,
              "column": 3
            },
            {
              "title": "no session → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1838,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8398,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/upi_pay.spec.js:34:16",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 16,
                          "line": 34
                        },
                        "snippet": "  32 |   test('no session → redirects to /hub/', async ({ page }) => {\n  33 |     await page.goto(BASE_URL);\n> 34 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  35 |     expect(page.url()).toContain('/hub/');\n  36 |   });\n  37 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 16,
                            "line": 34
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  32 |   test('no session → redirects to /hub/', async ({ page }) => {\n  33 |     await page.goto(BASE_URL);\n> 34 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  35 |     expect(page.url()).toContain('/hub/');\n  36 |   });\n  37 |\n    at /var/www/360lm/tests/upi_pay.spec.js:34:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:23.801Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-no-session-→-redirects-to-hub--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-no-session-→-redirects-to-hub--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 16,
                        "line": 34
                      }
                    },
                    {
                      "workerIndex": 1839,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8340,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/upi_pay.spec.js:34:16",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 16,
                          "line": 34
                        },
                        "snippet": "  32 |   test('no session → redirects to /hub/', async ({ page }) => {\n  33 |     await page.goto(BASE_URL);\n> 34 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  35 |     expect(page.url()).toContain('/hub/');\n  36 |   });\n  37 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 16,
                            "line": 34
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  32 |   test('no session → redirects to /hub/', async ({ page }) => {\n  33 |     await page.goto(BASE_URL);\n> 34 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  35 |     expect(page.url()).toContain('/hub/');\n  36 |   });\n  37 |\n    at /var/www/360lm/tests/upi_pay.spec.js:34:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:58:33.497Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-no-session-→-redirects-to-hub--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-no-session-→-redirects-to-hub--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 16,
                        "line": 34
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-abef12a832604cf6505e",
              "file": "upi_pay.spec.js",
              "line": 32,
              "column": 3
            },
            {
              "title": "expired session → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1840,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8295,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/upi_pay.spec.js:42:16",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 16,
                          "line": 42
                        },
                        "snippet": "  40 |     await injectSession(page, expired);\n  41 |     await page.goto(BASE_URL);\n> 42 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  43 |     expect(page.url()).toContain('/hub/');\n  44 |   });\n  45 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 16,
                            "line": 42
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  40 |     await injectSession(page, expired);\n  41 |     await page.goto(BASE_URL);\n> 42 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  43 |     expect(page.url()).toContain('/hub/');\n  44 |   });\n  45 |\n    at /var/www/360lm/tests/upi_pay.spec.js:42:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:58:43.006Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-expired-session-→-redirects-to-hub--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-expired-session-→-redirects-to-hub--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 16,
                        "line": 42
                      }
                    },
                    {
                      "workerIndex": 1841,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8350,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n    at /var/www/360lm/tests/upi_pay.spec.js:42:16",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 16,
                          "line": 42
                        },
                        "snippet": "  40 |     await injectSession(page, expired);\n  41 |     await page.goto(BASE_URL);\n> 42 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  43 |     expect(page.url()).toContain('/hub/');\n  44 |   });\n  45 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 16,
                            "line": 42
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 8000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation to \"**/hub/**\" until \"load\"\n============================================================\n\n  40 |     await injectSession(page, expired);\n  41 |     await page.goto(BASE_URL);\n> 42 |     await page.waitForURL('**/hub/**', { timeout: 8000 });\n     |                ^\n  43 |     expect(page.url()).toContain('/hub/');\n  44 |   });\n  45 |\n    at /var/www/360lm/tests/upi_pay.spec.js:42:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:58:52.440Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-expired-session-→-redirects-to-hub--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-expired-session-→-redirects-to-hub--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 16,
                        "line": 42
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-688301cb9956187ec4f6",
              "file": "upi_pay.spec.js",
              "line": 38,
              "column": 3
            },
            {
              "title": "non-custodian employee → Access Restricted screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1842,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5435,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:49:58",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 58,
                          "line": 49
                        },
                        "snippet": "  47 |     await injectSession(page, SESSION.mukesh);\n  48 |     await page.goto(BASE_URL);\n> 49 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 5000 });\n     |                                                          ^\n  50 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  51 |   });\n  52 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 58,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  47 |     await injectSession(page, SESSION.mukesh);\n  48 |     await page.goto(BASE_URL);\n> 49 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 5000 });\n     |                                                          ^\n  50 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  51 |   });\n  52 |\n    at /var/www/360lm/tests/upi_pay.spec.js:49:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:59:02.864Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-non-cust-3cd81--→-Access-Restricted-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-non-cust-3cd81--→-Access-Restricted-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 58,
                        "line": 49
                      }
                    },
                    {
                      "workerIndex": 1843,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 5304,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:49:58",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 58,
                          "line": 49
                        },
                        "snippet": "  47 |     await injectSession(page, SESSION.mukesh);\n  48 |     await page.goto(BASE_URL);\n> 49 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 5000 });\n     |                                                          ^\n  50 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  51 |   });\n  52 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 58,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  47 |     await injectSession(page, SESSION.mukesh);\n  48 |     await page.goto(BASE_URL);\n> 49 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 5000 });\n     |                                                          ^\n  50 |     await expect(page.locator('a[href=\"/hub/\"]')).toBeVisible();\n  51 |   });\n  52 |\n    at /var/www/360lm/tests/upi_pay.spec.js:49:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:59:09.494Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-non-cust-3cd81--→-Access-Restricted-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-non-cust-3cd81--→-Access-Restricted-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 58,
                        "line": 49
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-6cee3c2dd44df04209af",
              "file": "upi_pay.spec.js",
              "line": 46,
              "column": 3
            },
            {
              "title": "custodian (pramod) → dashboard loads",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1844,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6276,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:56:54",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 54,
                          "line": 56
                        },
                        "snippet": "  54 |     await injectSession(page, SESSION.pramod);\n  55 |     await page.goto(BASE_URL);\n> 56 |     await expect(page.locator('#s-dash .hdr-title')).toContainText('UPI Pay', { timeout: 6000 });\n     |                                                      ^\n  57 |     await expect(page.locator('.btn-cta')).toBeVisible();\n  58 |   });\n  59 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 54,
                            "line": 56
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n\n\n  54 |     await injectSession(page, SESSION.pramod);\n  55 |     await page.goto(BASE_URL);\n> 56 |     await expect(page.locator('#s-dash .hdr-title')).toContainText('UPI Pay', { timeout: 6000 });\n     |                                                      ^\n  57 |     await expect(page.locator('.btn-cta')).toBeVisible();\n  58 |   });\n  59 | });\n    at /var/www/360lm/tests/upi_pay.spec.js:56:54"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:59:15.958Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-custodian-pramod-→-dashboard-loads-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-custodian-pramod-→-dashboard-loads-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 54,
                        "line": 56
                      }
                    },
                    {
                      "workerIndex": 1845,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6246,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:56:54",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 54,
                          "line": 56
                        },
                        "snippet": "  54 |     await injectSession(page, SESSION.pramod);\n  55 |     await page.goto(BASE_URL);\n> 56 |     await expect(page.locator('#s-dash .hdr-title')).toContainText('UPI Pay', { timeout: 6000 });\n     |                                                      ^\n  57 |     await expect(page.locator('.btn-cta')).toBeVisible();\n  58 |   });\n  59 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 54,
                            "line": 56
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-dash .hdr-title')\nExpected substring: \u001b[32m\"UPI Pay\"\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-dash .hdr-title')\u001b[22m\n\n\n  54 |     await injectSession(page, SESSION.pramod);\n  55 |     await page.goto(BASE_URL);\n> 56 |     await expect(page.locator('#s-dash .hdr-title')).toContainText('UPI Pay', { timeout: 6000 });\n     |                                                      ^\n  57 |     await expect(page.locator('.btn-cta')).toBeVisible();\n  58 |   });\n  59 | });\n    at /var/www/360lm/tests/upi_pay.spec.js:56:54"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:59:23.409Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-custodian-pramod-→-dashboard-loads-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Auth-Gate-custodian-pramod-→-dashboard-loads-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 54,
                        "line": 56
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-7281969842d30aedbfa4",
              "file": "upi_pay.spec.js",
              "line": 53,
              "column": 3
            }
          ]
        },
        {
          "title": "Dashboard",
          "file": "upi_pay.spec.js",
          "line": 64,
          "column": 6,
          "specs": [
            {
              "title": "balance card renders",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 838,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6248,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:59:18.853Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-balance-card-renders-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-balance-card-renders-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    },
                    {
                      "workerIndex": 839,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6228,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:59:26.108Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-balance-card-renders-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-balance-card-renders-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-26af967fd1d89432de15",
              "file": "upi_pay.spec.js",
              "line": 71,
              "column": 3
            },
            {
              "title": "recent-list section renders (empty state or rows)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 840,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6223,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:59:33.304Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-recent-l-01d91-enders-empty-state-or-rows--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-recent-l-01d91-enders-empty-state-or-rows--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    },
                    {
                      "workerIndex": 841,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6259,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:59:40.511Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-recent-l-01d91-enders-empty-state-or-rows--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-recent-l-01d91-enders-empty-state-or-rows--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-8562589018df24416f5a",
              "file": "upi_pay.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "Custodian button links to /finance/custodian/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 842,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6253,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T20:59:47.867Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Custodia-79eae-links-to-finance-custodian--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Custodia-79eae-links-to-finance-custodian--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    },
                    {
                      "workerIndex": 843,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6519,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T20:59:55.151Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Custodia-79eae-links-to-finance-custodian--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Custodia-79eae-links-to-finance-custodian--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-20e92ef81504469cfbd3",
              "file": "upi_pay.spec.js",
              "line": 83,
              "column": 3
            },
            {
              "title": "Hub button links to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 844,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6755,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:00:04.942Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Hub-button-links-to-hub--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Hub-button-links-to-hub--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    },
                    {
                      "workerIndex": 845,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6292,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:00:13.592Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Hub-button-links-to-hub--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Hub-button-links-to-hub--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-9b094f48fc55ffae3dff",
              "file": "upi_pay.spec.js",
              "line": 88,
              "column": 3
            },
            {
              "title": "balance card renders",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1846,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6276,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:59:30.734Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-balance-card-renders-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-balance-card-renders-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    },
                    {
                      "workerIndex": 1847,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6274,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:59:38.084Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-balance-card-renders-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-balance-card-renders-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-f8e72853f11ed556a96c",
              "file": "upi_pay.spec.js",
              "line": 71,
              "column": 3
            },
            {
              "title": "recent-list section renders (empty state or rows)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1848,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6290,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T23:59:45.325Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-recent-l-01d91-enders-empty-state-or-rows--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-recent-l-01d91-enders-empty-state-or-rows--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    },
                    {
                      "workerIndex": 1849,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6293,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T23:59:52.642Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-recent-l-01d91-enders-empty-state-or-rows--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-recent-l-01d91-enders-empty-state-or-rows--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-edf522749e307791c751",
              "file": "upi_pay.spec.js",
              "line": 76,
              "column": 3
            },
            {
              "title": "Custodian button links to /finance/custodian/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1850,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7372,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:00:00.193Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Custodia-79eae-links-to-finance-custodian--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Custodia-79eae-links-to-finance-custodian--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    },
                    {
                      "workerIndex": 1851,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6478,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:00:15.915Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Custodia-79eae-links-to-finance-custodian--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Custodia-79eae-links-to-finance-custodian--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-ff141b7eb9a392585b9b",
              "file": "upi_pay.spec.js",
              "line": 83,
              "column": 3
            },
            {
              "title": "Hub button links to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1852,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7507,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:00:23.741Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Hub-button-links-to-hub--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Hub-button-links-to-hub--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    },
                    {
                      "workerIndex": 1853,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6988,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 44,
                          "line": 68
                        },
                        "snippet": "  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 44,
                            "line": 68
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.btn-cta')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  66 |     await injectSession(page, SESSION.pramod);\n  67 |     await page.goto(BASE_URL);\n> 68 |     await expect(page.locator('.btn-cta')).toBeVisible({ timeout: 6000 });\n     |                                            ^\n  69 |   });\n  70 |\n  71 |   test('balance card renders', async ({ page }) => {\n    at /var/www/360lm/tests/upi_pay.spec.js:68:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:00:43.500Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Hub-button-links-to-hub--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Dashboard-Hub-button-links-to-hub--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 44,
                        "line": 68
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-b946097adca09d2d0d53",
              "file": "upi_pay.spec.js",
              "line": 88,
              "column": 3
            }
          ]
        },
        {
          "title": "New Payment Screen — Navigation",
          "file": "upi_pay.spec.js",
          "line": 97,
          "column": 6,
          "specs": [
            {
              "title": "opens payment screen when CTA tapped",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 846,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30068,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 98
                        },
                        "snippet": "   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 98
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 101
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n> 101 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  102 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  103 |   });\n  104 |\n    at /var/www/360lm/tests/upi_pay.spec.js:101:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:00:21.088Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-851d8-ment-screen-when-CTA-tapped-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-851d8-ment-screen-when-CTA-tapped-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 98
                      }
                    },
                    {
                      "workerIndex": 847,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 98
                        },
                        "snippet": "   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 98
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 101
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n> 101 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  102 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  103 |   });\n  104 |\n    at /var/www/360lm/tests/upi_pay.spec.js:101:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:00:52.272Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-851d8-ment-screen-when-CTA-tapped-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-851d8-ment-screen-when-CTA-tapped-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 98
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-81560fc51eca40d691f4",
              "file": "upi_pay.spec.js",
              "line": 105,
              "column": 3
            },
            {
              "title": "back button returns to dashboard",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 848,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 98
                        },
                        "snippet": "   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 98
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 101
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n> 101 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  102 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  103 |   });\n  104 |\n    at /var/www/360lm/tests/upi_pay.spec.js:101:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:01:23.337Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-c8909-button-returns-to-dashboard-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-c8909-button-returns-to-dashboard-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 98
                      }
                    },
                    {
                      "workerIndex": 849,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30061,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 98
                        },
                        "snippet": "   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 98
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 101
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n> 101 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  102 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  103 |   });\n  104 |\n    at /var/www/360lm/tests/upi_pay.spec.js:101:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:01:54.370Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-c8909-button-returns-to-dashboard-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-c8909-button-returns-to-dashboard-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 98
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-927ea8bcaeb2d62ad62c",
              "file": "upi_pay.spec.js",
              "line": 110,
              "column": 3
            },
            {
              "title": "opens payment screen when CTA tapped",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1854,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30120,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 98
                        },
                        "snippet": "   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 98
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 101
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n> 101 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  102 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  103 |   });\n  104 |\n    at /var/www/360lm/tests/upi_pay.spec.js:101:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:00:52.523Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-851d8-ment-screen-when-CTA-tapped-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-851d8-ment-screen-when-CTA-tapped-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 98
                      }
                    },
                    {
                      "workerIndex": 1855,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30188,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 98
                        },
                        "snippet": "   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 98
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 101
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n> 101 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  102 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  103 |   });\n  104 |\n    at /var/www/360lm/tests/upi_pay.spec.js:101:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:01:24.417Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-851d8-ment-screen-when-CTA-tapped-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-851d8-ment-screen-when-CTA-tapped-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 98
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-6fe209bfcdb418f341b6",
              "file": "upi_pay.spec.js",
              "line": 105,
              "column": 3
            },
            {
              "title": "back button returns to dashboard",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1856,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30172,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 98
                        },
                        "snippet": "   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 98
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 101
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n> 101 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  102 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  103 |   });\n  104 |\n    at /var/www/360lm/tests/upi_pay.spec.js:101:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:01:56.171Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-c8909-button-returns-to-dashboard-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-c8909-button-returns-to-dashboard-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 98
                      }
                    },
                    {
                      "workerIndex": 1857,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30106,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 98
                        },
                        "snippet": "   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 98
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n   96 | // ─────────────────────────────────────────────────────────────────\n   97 | test.describe('New Payment Screen — Navigation', () => {\n>  98 |   test.beforeEach(async ({ page }) => {\n      |        ^\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n  101 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:98:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 101
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n   99 |     await injectSession(page, SESSION.pramod);\n  100 |     await page.goto(BASE_URL);\n> 101 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  102 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  103 |   });\n  104 |\n    at /var/www/360lm/tests/upi_pay.spec.js:101:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:02:27.840Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-c8909-button-returns-to-dashboard-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-New-Payment-Screen-c8909-button-returns-to-dashboard-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 98
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-64423650aba244a300e7",
              "file": "upi_pay.spec.js",
              "line": 110,
              "column": 3
            }
          ]
        },
        {
          "title": "Reason Chips",
          "file": "upi_pay.spec.js",
          "line": 119,
          "column": 6,
          "specs": [
            {
              "title": "Exp. Settlement chip selectable",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 850,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:02:25.384Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Exp-Settlement-chip-selectable-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Exp-Settlement-chip-selectable-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    },
                    {
                      "workerIndex": 851,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:02:56.407Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Exp-Settlement-chip-selectable-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Exp-Settlement-chip-selectable-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-347b3c5ce5b6562b20be",
              "file": "upi_pay.spec.js",
              "line": 127,
              "column": 3
            },
            {
              "title": "Custodian Transfer chip shows notice",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 852,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30064,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:03:27.450Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Custodian-Transfer-chip-shows-notice-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Custodian-Transfer-chip-shows-notice-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    },
                    {
                      "workerIndex": 853,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30063,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:03:58.482Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Custodian-Transfer-chip-shows-notice-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Custodian-Transfer-chip-shows-notice-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-c8ae04edc55a37538b0b",
              "file": "upi_pay.spec.js",
              "line": 132,
              "column": 3
            },
            {
              "title": "switching reason hides Custodian Transfer notice",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 854,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30068,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:04:29.566Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-switc-9c414-s-Custodian-Transfer-notice-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-switc-9c414-s-Custodian-Transfer-notice-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    },
                    {
                      "workerIndex": 855,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:05:00.724Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-switc-9c414-s-Custodian-Transfer-notice-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-switc-9c414-s-Custodian-Transfer-notice-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-7ed1d27e7809bec1be2c",
              "file": "upi_pay.spec.js",
              "line": 137,
              "column": 3
            },
            {
              "title": "Exp. Settlement chip selectable",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1858,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30073,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:02:59.244Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Exp-Settlement-chip-selectable-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Exp-Settlement-chip-selectable-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    },
                    {
                      "workerIndex": 1859,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30097,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:03:30.357Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Exp-Settlement-chip-selectable-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Exp-Settlement-chip-selectable-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-2e5e51d4c3c8fe52928e",
              "file": "upi_pay.spec.js",
              "line": 127,
              "column": 3
            },
            {
              "title": "Custodian Transfer chip shows notice",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1860,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30085,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:04:02.367Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Custodian-Transfer-chip-shows-notice-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Custodian-Transfer-chip-shows-notice-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    },
                    {
                      "workerIndex": 1861,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30159,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:04:33.713Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Custodian-Transfer-chip-shows-notice-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-Custodian-Transfer-chip-shows-notice-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-a591a08771966ddf0f6b",
              "file": "upi_pay.spec.js",
              "line": 132,
              "column": 3
            },
            {
              "title": "switching reason hides Custodian Transfer notice",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1862,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30091,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:05:05.169Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-switc-9c414-s-Custodian-Transfer-notice-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-switc-9c414-s-Custodian-Transfer-notice-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    },
                    {
                      "workerIndex": 1863,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 120
                        },
                        "snippet": "  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 120
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  118 | // ─────────────────────────────────────────────────────────────────\n  119 | test.describe('Reason Chips', () => {\n> 120 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n  123 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:120:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 123
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  121 |     await injectSession(page, SESSION.pramod);\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  124 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  125 |   });\n  126 |\n    at /var/www/360lm/tests/upi_pay.spec.js:123:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:05:36.326Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-switc-9c414-s-Custodian-Transfer-notice-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Reason-Chips-switc-9c414-s-Custodian-Transfer-notice-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 120
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-284073ed85249ae5f4a7",
              "file": "upi_pay.spec.js",
              "line": 137,
              "column": 3
            }
          ]
        },
        {
          "title": "Mode Chips",
          "file": "upi_pay.spec.js",
          "line": 148,
          "column": 6,
          "specs": [
            {
              "title": "UPI selected by default, UPI section visible",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 856,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30292,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:05:31.874Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-sel-358df-default-UPI-section-visible-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-sel-358df-default-UPI-section-visible-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 857,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30068,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:06:03.546Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-sel-358df-default-UPI-section-visible-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-sel-358df-default-UPI-section-visible-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-d56e89bdd1af6b1db2e9",
              "file": "upi_pay.spec.js",
              "line": 156,
              "column": 3
            },
            {
              "title": "NEFT mode: UPI section hidden, ref label is UTR No.",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 858,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30069,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:06:34.571Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-NEFT-mo-d59f9-hidden-ref-label-is-UTR-No--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-NEFT-mo-d59f9-hidden-ref-label-is-UTR-No--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 859,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:07:05.699Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-NEFT-mo-d59f9-hidden-ref-label-is-UTR-No--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-NEFT-mo-d59f9-hidden-ref-label-is-UTR-No--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-8e2f14f26d0d3bde4a15",
              "file": "upi_pay.spec.js",
              "line": 161,
              "column": 3
            },
            {
              "title": "RTGS mode: UPI section hidden, ref label is UTR No.",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 860,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30085,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:07:36.760Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-RTGS-mo-a02d5-hidden-ref-label-is-UTR-No--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-RTGS-mo-a02d5-hidden-ref-label-is-UTR-No--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 861,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30095,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:08:07.852Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-RTGS-mo-a02d5-hidden-ref-label-is-UTR-No--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-RTGS-mo-a02d5-hidden-ref-label-is-UTR-No--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-98db1aefb534cd2f0539",
              "file": "upi_pay.spec.js",
              "line": 167,
              "column": 3
            },
            {
              "title": "IMPS mode: UPI section hidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 862,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30104,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:08:39.078Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-IMPS-mode-UPI-section-hidden-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-IMPS-mode-UPI-section-hidden-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 863,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:09:10.226Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-IMPS-mode-UPI-section-hidden-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-IMPS-mode-UPI-section-hidden-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-973bcd0d079145bf7dab",
              "file": "upi_pay.spec.js",
              "line": 173,
              "column": 3
            },
            {
              "title": "UPI mode: ref label is UPI Ref No.",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 864,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30081,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:09:41.374Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-mode-ref-label-is-UPI-Ref-No--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-mode-ref-label-is-UPI-Ref-No--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 865,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30067,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:10:12.540Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-mode-ref-label-is-UPI-Ref-No--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-mode-ref-label-is-UPI-Ref-No--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-1184c3bf3f1684f44099",
              "file": "upi_pay.spec.js",
              "line": 178,
              "column": 3
            },
            {
              "title": "switching back to UPI shows UPI section",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 866,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30071,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:10:43.594Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-switching-back-to-UPI-shows-UPI-section-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-switching-back-to-UPI-shows-UPI-section-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 867,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30086,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:11:14.632Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-switching-back-to-UPI-shows-UPI-section-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-switching-back-to-UPI-shows-UPI-section-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-efcf771171408b11fb42",
              "file": "upi_pay.spec.js",
              "line": 182,
              "column": 3
            },
            {
              "title": "UPI selected by default, UPI section visible",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1864,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30089,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:06:07.569Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-sel-358df-default-UPI-section-visible-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-sel-358df-default-UPI-section-visible-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 1865,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:06:38.665Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-sel-358df-default-UPI-section-visible-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-sel-358df-default-UPI-section-visible-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-b72878bdace4e8285ad0",
              "file": "upi_pay.spec.js",
              "line": 156,
              "column": 3
            },
            {
              "title": "NEFT mode: UPI section hidden, ref label is UTR No.",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1866,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30081,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:07:09.727Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-NEFT-mo-d59f9-hidden-ref-label-is-UTR-No--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-NEFT-mo-d59f9-hidden-ref-label-is-UTR-No--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 1867,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30091,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:07:40.820Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-NEFT-mo-d59f9-hidden-ref-label-is-UTR-No--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-NEFT-mo-d59f9-hidden-ref-label-is-UTR-No--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-85f3dbfc36ab3f163f32",
              "file": "upi_pay.spec.js",
              "line": 161,
              "column": 3
            },
            {
              "title": "RTGS mode: UPI section hidden, ref label is UTR No.",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1868,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30097,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:08:11.863Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-RTGS-mo-a02d5-hidden-ref-label-is-UTR-No--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-RTGS-mo-a02d5-hidden-ref-label-is-UTR-No--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 1869,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30093,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:08:42.906Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-RTGS-mo-a02d5-hidden-ref-label-is-UTR-No--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-RTGS-mo-a02d5-hidden-ref-label-is-UTR-No--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-4354c017a47dab50601b",
              "file": "upi_pay.spec.js",
              "line": 167,
              "column": 3
            },
            {
              "title": "IMPS mode: UPI section hidden",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1870,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:09:14.049Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-IMPS-mode-UPI-section-hidden-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-IMPS-mode-UPI-section-hidden-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 1871,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30083,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:09:45.135Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-IMPS-mode-UPI-section-hidden-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-IMPS-mode-UPI-section-hidden-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-cd857084b957d85ed9bb",
              "file": "upi_pay.spec.js",
              "line": 173,
              "column": 3
            },
            {
              "title": "UPI mode: ref label is UPI Ref No.",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1872,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30064,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:10:16.243Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-mode-ref-label-is-UPI-Ref-No--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-mode-ref-label-is-UPI-Ref-No--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 1873,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30090,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:10:47.395Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-mode-ref-label-is-UPI-Ref-No--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-UPI-mode-ref-label-is-UPI-Ref-No--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-d95fa0f74616ef99c8de",
              "file": "upi_pay.spec.js",
              "line": 178,
              "column": 3
            },
            {
              "title": "switching back to UPI shows UPI section",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1874,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30096,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:11:18.548Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-switching-back-to-UPI-shows-UPI-section-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-switching-back-to-UPI-shows-UPI-section-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    },
                    {
                      "workerIndex": 1875,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 149
                        },
                        "snippet": "  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 149
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  147 | // ─────────────────────────────────────────────────────────────────\n  148 | test.describe('Mode Chips', () => {\n> 149 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n  152 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:149:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 152
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  150 |     await injectSession(page, SESSION.pramod);\n  151 |     await page.goto(BASE_URL);\n> 152 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  153 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  154 |   });\n  155 |\n    at /var/www/360lm/tests/upi_pay.spec.js:152:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:11:49.742Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-switching-back-to-UPI-shows-UPI-section-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Mode-Chips-switching-back-to-UPI-shows-UPI-section-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 149
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-735ceee039365ed5bd3f",
              "file": "upi_pay.spec.js",
              "line": 182,
              "column": 3
            }
          ]
        },
        {
          "title": "UPI App Picker",
          "file": "upi_pay.spec.js",
          "line": 192,
          "column": 6,
          "specs": [
            {
              "title": "4 UPI app buttons rendered",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 868,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30073,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 193
                        },
                        "snippet": "  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 193
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 196
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n> 196 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  197 |     await expect(page.locator('#upi-app-row')).toBeVisible({ timeout: 3000 });\n  198 |   });\n  199 |\n    at /var/www/360lm/tests/upi_pay.spec.js:196:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:11:45.673Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-4-UPI-app-buttons-rendered-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-4-UPI-app-buttons-rendered-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 193
                      }
                    },
                    {
                      "workerIndex": 869,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30097,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 193
                        },
                        "snippet": "  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 193
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 196
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n> 196 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  197 |     await expect(page.locator('#upi-app-row')).toBeVisible({ timeout: 3000 });\n  198 |   });\n  199 |\n    at /var/www/360lm/tests/upi_pay.spec.js:196:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:12:16.708Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-4-UPI-app-buttons-rendered-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-4-UPI-app-buttons-rendered-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 193
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-cb2e56b232b494c8fe32",
              "file": "upi_pay.spec.js",
              "line": 200,
              "column": 3
            },
            {
              "title": "tapping an app button selects it",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 870,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30065,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 193
                        },
                        "snippet": "  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 193
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 196
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n> 196 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  197 |     await expect(page.locator('#upi-app-row')).toBeVisible({ timeout: 3000 });\n  198 |   });\n  199 |\n    at /var/www/360lm/tests/upi_pay.spec.js:196:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:12:47.903Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-tapping-an-app-button-selects-it-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-tapping-an-app-button-selects-it-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 193
                      }
                    },
                    {
                      "workerIndex": 871,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30083,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 193
                        },
                        "snippet": "  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 193
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 196
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n> 196 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  197 |     await expect(page.locator('#upi-app-row')).toBeVisible({ timeout: 3000 });\n  198 |   });\n  199 |\n    at /var/www/360lm/tests/upi_pay.spec.js:196:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:13:19.049Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-tapping-an-app-button-selects-it-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-tapping-an-app-button-selects-it-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 193
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-0d7a3bff996aad6f4cf8",
              "file": "upi_pay.spec.js",
              "line": 205,
              "column": 3
            },
            {
              "title": "4 UPI app buttons rendered",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1876,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30092,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 193
                        },
                        "snippet": "  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 193
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 196
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n> 196 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  197 |     await expect(page.locator('#upi-app-row')).toBeVisible({ timeout: 3000 });\n  198 |   });\n  199 |\n    at /var/www/360lm/tests/upi_pay.spec.js:196:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:12:20.856Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-4-UPI-app-buttons-rendered-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-4-UPI-app-buttons-rendered-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 193
                      }
                    },
                    {
                      "workerIndex": 1877,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30138,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 193
                        },
                        "snippet": "  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 193
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 196
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n> 196 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  197 |     await expect(page.locator('#upi-app-row')).toBeVisible({ timeout: 3000 });\n  198 |   });\n  199 |\n    at /var/www/360lm/tests/upi_pay.spec.js:196:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:12:51.970Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-4-UPI-app-buttons-rendered-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-4-UPI-app-buttons-rendered-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 193
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-57aea7e9c0ab449be888",
              "file": "upi_pay.spec.js",
              "line": 200,
              "column": 3
            },
            {
              "title": "tapping an app button selects it",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1878,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30075,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 193
                        },
                        "snippet": "  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 193
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 196
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n> 196 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  197 |     await expect(page.locator('#upi-app-row')).toBeVisible({ timeout: 3000 });\n  198 |   });\n  199 |\n    at /var/www/360lm/tests/upi_pay.spec.js:196:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:13:23.203Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-tapping-an-app-button-selects-it-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-tapping-an-app-button-selects-it-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 193
                      }
                    },
                    {
                      "workerIndex": 1879,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30072,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 193
                        },
                        "snippet": "  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 193
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  191 | // ─────────────────────────────────────────────────────────────────\n  192 | test.describe('UPI App Picker', () => {\n> 193 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n  196 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:193:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 196
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  194 |     await injectSession(page, SESSION.pramod);\n  195 |     await page.goto(BASE_URL);\n> 196 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  197 |     await expect(page.locator('#upi-app-row')).toBeVisible({ timeout: 3000 });\n  198 |   });\n  199 |\n    at /var/www/360lm/tests/upi_pay.spec.js:196:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:13:54.288Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-tapping-an-app-button-selects-it-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-UPI-App-Picker-tapping-an-app-button-selects-it-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 193
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-6a747e681163ea476d73",
              "file": "upi_pay.spec.js",
              "line": 205,
              "column": 3
            }
          ]
        },
        {
          "title": "Payee Search",
          "file": "upi_pay.spec.js",
          "line": 215,
          "column": 6,
          "specs": [
            {
              "title": "typing 2+ chars triggers search and shows dropdown",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 872,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30070,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:13:50.137Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-typin-f476a-s-search-and-shows-dropdown-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-typin-f476a-s-search-and-shows-dropdown-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 873,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30092,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:14:21.196Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-typin-f476a-s-search-and-shows-dropdown-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-typin-f476a-s-search-and-shows-dropdown-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-4382ec221b8f27eb9727",
              "file": "upi_pay.spec.js",
              "line": 223,
              "column": 3
            },
            {
              "title": "selecting a result shows payee card",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 874,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30083,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:14:52.238Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-selecting-a-result-shows-payee-card-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-selecting-a-result-shows-payee-card-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 875,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30080,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:15:23.317Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-selecting-a-result-shows-payee-card-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-selecting-a-result-shows-payee-card-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-94498055a227513c9784",
              "file": "upi_pay.spec.js",
              "line": 230,
              "column": 3
            },
            {
              "title": "clear button removes selected payee",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 876,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30065,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:15:54.589Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-clear-button-removes-selected-payee-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-clear-button-removes-selected-payee-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 877,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30103,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:16:25.639Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-clear-button-removes-selected-payee-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-clear-button-removes-selected-payee-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-b0bd42f8cbb454f65a8c",
              "file": "upi_pay.spec.js",
              "line": 238,
              "column": 3
            },
            {
              "title": "searching for harish returns Harish Kumar Lal",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 878,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30063,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:16:56.759Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-searc-3551a-sh-returns-Harish-Kumar-Lal-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-searc-3551a-sh-returns-Harish-Kumar-Lal-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 879,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30070,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:17:27.762Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-searc-3551a-sh-returns-Harish-Kumar-Lal-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-searc-3551a-sh-returns-Harish-Kumar-Lal-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-2d75c75c2f1a9b0cd237",
              "file": "upi_pay.spec.js",
              "line": 247,
              "column": 3
            },
            {
              "title": "typing 2+ chars triggers search and shows dropdown",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1880,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30060,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:14:25.366Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-typin-f476a-s-search-and-shows-dropdown-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-typin-f476a-s-search-and-shows-dropdown-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 1881,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30086,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:14:56.369Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-typin-f476a-s-search-and-shows-dropdown-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-typin-f476a-s-search-and-shows-dropdown-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-9e3b26e27c99e54ea624",
              "file": "upi_pay.spec.js",
              "line": 223,
              "column": 3
            },
            {
              "title": "selecting a result shows payee card",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1882,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30068,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:15:27.504Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-selecting-a-result-shows-payee-card-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-selecting-a-result-shows-payee-card-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 1883,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30083,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:15:58.721Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-selecting-a-result-shows-payee-card-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-selecting-a-result-shows-payee-card-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-16b0327c275a8de2e7da",
              "file": "upi_pay.spec.js",
              "line": 230,
              "column": 3
            },
            {
              "title": "clear button removes selected payee",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1884,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30104,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:16:29.842Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-clear-button-removes-selected-payee-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-clear-button-removes-selected-payee-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 1885,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30071,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:17:01.313Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-clear-button-removes-selected-payee-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-clear-button-removes-selected-payee-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-09e5ce7e11fc5b7ced59",
              "file": "upi_pay.spec.js",
              "line": 238,
              "column": 3
            },
            {
              "title": "searching for harish returns Harish Kumar Lal",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1886,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30191,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:17:32.588Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-searc-3551a-sh-returns-Harish-Kumar-Lal-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-searc-3551a-sh-returns-Harish-Kumar-Lal-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 1887,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30070,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 216
                        },
                        "snippet": "  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 216
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  214 | // ─────────────────────────────────────────────────────────────────\n  215 | test.describe('Payee Search', () => {\n> 216 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n  219 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:216:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 219
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  217 |     await injectSession(page, SESSION.pramod);\n  218 |     await page.goto(BASE_URL);\n> 219 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  220 |     await expect(page.locator('#payee-q')).toBeVisible({ timeout: 3000 });\n  221 |   });\n  222 |\n    at /var/www/360lm/tests/upi_pay.spec.js:219:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:18:04.307Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-searc-3551a-sh-returns-Harish-Kumar-Lal-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Payee-Search-searc-3551a-sh-returns-Harish-Kumar-Lal-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-90e3aabe898e79cefe5c",
              "file": "upi_pay.spec.js",
              "line": 247,
              "column": 3
            }
          ]
        },
        {
          "title": "Form Validation",
          "file": "upi_pay.spec.js",
          "line": 257,
          "column": 6,
          "specs": [
            {
              "title": "submit with no payee shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 880,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30083,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:17:58.875Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-bf30f--no-payee-shows-error-toast-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-bf30f--no-payee-shows-error-toast-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    },
                    {
                      "workerIndex": 881,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30123,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:18:30.055Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-bf30f--no-payee-shows-error-toast-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-bf30f--no-payee-shows-error-toast-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-69b3071bf89be3396041",
              "file": "upi_pay.spec.js",
              "line": 265,
              "column": 3
            },
            {
              "title": "submit with no amount shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 882,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30073,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:19:01.418Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-631e3-no-amount-shows-error-toast-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-631e3-no-amount-shows-error-toast-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    },
                    {
                      "workerIndex": 883,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30382,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:19:32.753Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-631e3-no-amount-shows-error-toast-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-631e3-no-amount-shows-error-toast-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-add06587e41c9eddb834",
              "file": "upi_pay.spec.js",
              "line": 272,
              "column": 3
            },
            {
              "title": "submit with no reason shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 884,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30082,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:20:05.348Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-4c18f-no-reason-shows-error-toast-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-4c18f-no-reason-shows-error-toast-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    },
                    {
                      "workerIndex": 885,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:20:36.559Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-4c18f-no-reason-shows-error-toast-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-4c18f-no-reason-shows-error-toast-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-b066608e129fe2877855",
              "file": "upi_pay.spec.js",
              "line": 281,
              "column": 3
            },
            {
              "title": "clear form resets all fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 886,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30078,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:21:07.669Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-clear-form-resets-all-fields-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-clear-form-resets-all-fields-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    },
                    {
                      "workerIndex": 887,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30079,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:21:38.743Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-clear-form-resets-all-fields-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-clear-form-resets-all-fields-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-1929783505d7ba9ef1d0",
              "file": "upi_pay.spec.js",
              "line": 290,
              "column": 3
            },
            {
              "title": "submit with no payee shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1888,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30089,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:18:35.373Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-bf30f--no-payee-shows-error-toast-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-bf30f--no-payee-shows-error-toast-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    },
                    {
                      "workerIndex": 1889,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:19:06.561Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-bf30f--no-payee-shows-error-toast-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-bf30f--no-payee-shows-error-toast-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-5e478fe6adb1fc6bb817",
              "file": "upi_pay.spec.js",
              "line": 265,
              "column": 3
            },
            {
              "title": "submit with no amount shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1890,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30095,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:19:37.599Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-631e3-no-amount-shows-error-toast-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-631e3-no-amount-shows-error-toast-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    },
                    {
                      "workerIndex": 1891,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30097,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:20:08.983Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-631e3-no-amount-shows-error-toast-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-631e3-no-amount-shows-error-toast-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-e58837cf3ce9db740796",
              "file": "upi_pay.spec.js",
              "line": 272,
              "column": 3
            },
            {
              "title": "submit with no reason shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1892,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30085,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:20:40.138Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-4c18f-no-reason-shows-error-toast-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-4c18f-no-reason-shows-error-toast-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    },
                    {
                      "workerIndex": 1893,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30090,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:21:11.246Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-4c18f-no-reason-shows-error-toast-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-su-4c18f-no-reason-shows-error-toast-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-98b0b0bb15007a8b862c",
              "file": "upi_pay.spec.js",
              "line": 281,
              "column": 3
            },
            {
              "title": "clear form resets all fields",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1894,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30067,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:21:42.304Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-clear-form-resets-all-fields-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-clear-form-resets-all-fields-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    },
                    {
                      "workerIndex": 1895,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30086,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 258
                        },
                        "snippet": "  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 258
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  256 | // ─────────────────────────────────────────────────────────────────\n  257 | test.describe('Form Validation', () => {\n> 258 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n  261 |     await page.locator('.btn-cta').click();\n    at /var/www/360lm/tests/upi_pay.spec.js:258:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 261
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  259 |     await injectSession(page, SESSION.pramod);\n  260 |     await page.goto(BASE_URL);\n> 261 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  262 |     await expect(page.locator('#pay-submit')).toBeVisible({ timeout: 3000 });\n  263 |   });\n  264 |\n    at /var/www/360lm/tests/upi_pay.spec.js:261:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:22:13.302Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-clear-form-resets-all-fields-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Form-Validation-clear-form-resets-all-fields-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 258
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-f36c4b3babcdca04ee27",
              "file": "upi_pay.spec.js",
              "line": 290,
              "column": 3
            }
          ]
        },
        {
          "title": "URL Param Pre-fill",
          "file": "upi_pay.spec.js",
          "line": 306,
          "column": 6,
          "specs": [
            {
              "title": "context=custodian-transfer pre-selects reason and opens pay screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 888,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6253,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:310:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 310
                        },
                        "snippet": "  308 |     await injectSession(page, SESSION.pramod);\n  309 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=30000');\n> 310 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  311 |     await expect(page.locator('#reason-chips .chip', { hasText: 'Custodian Transfer' })).toHaveClass(/sel/);\n  312 |     await expect(page.locator('#pay-amount')).toHaveValue('30000');\n  313 |     await expect(page.locator('#reason-notice')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 310
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  308 |     await injectSession(page, SESSION.pramod);\n  309 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=30000');\n> 310 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  311 |     await expect(page.locator('#reason-chips .chip', { hasText: 'Custodian Transfer' })).toHaveClass(/sel/);\n  312 |     await expect(page.locator('#pay-amount')).toHaveValue('30000');\n  313 |     await expect(page.locator('#reason-notice')).toBeVisible();\n    at /var/www/360lm/tests/upi_pay.spec.js:310:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:22:09.900Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-76834-reason-and-opens-pay-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-76834-reason-and-opens-pay-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 310
                      }
                    },
                    {
                      "workerIndex": 889,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6274,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:310:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 310
                        },
                        "snippet": "  308 |     await injectSession(page, SESSION.pramod);\n  309 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=30000');\n> 310 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  311 |     await expect(page.locator('#reason-chips .chip', { hasText: 'Custodian Transfer' })).toHaveClass(/sel/);\n  312 |     await expect(page.locator('#pay-amount')).toHaveValue('30000');\n  313 |     await expect(page.locator('#reason-notice')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 310
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  308 |     await injectSession(page, SESSION.pramod);\n  309 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=30000');\n> 310 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  311 |     await expect(page.locator('#reason-chips .chip', { hasText: 'Custodian Transfer' })).toHaveClass(/sel/);\n  312 |     await expect(page.locator('#pay-amount')).toHaveValue('30000');\n  313 |     await expect(page.locator('#reason-notice')).toBeVisible();\n    at /var/www/360lm/tests/upi_pay.spec.js:310:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:22:17.241Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-76834-reason-and-opens-pay-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-76834-reason-and-opens-pay-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 310
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-bf08496b6898899712ca",
              "file": "upi_pay.spec.js",
              "line": 307,
              "column": 3
            },
            {
              "title": "today date is always pre-filled",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 890,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30081,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 319
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  317 |     await injectSession(page, SESSION.pramod);\n  318 |     await page.goto(BASE_URL);\n> 319 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  320 |     const today = new Date().toISOString().slice(0, 10);\n  321 |     await expect(page.locator('#pay-date')).toHaveValue(today);\n  322 |   });\n    at /var/www/360lm/tests/upi_pay.spec.js:319:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:22:24.466Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-today-date-is-always-pre-filled-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-today-date-is-always-pre-filled-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 891,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30129,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 319
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  317 |     await injectSession(page, SESSION.pramod);\n  318 |     await page.goto(BASE_URL);\n> 319 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  320 |     const today = new Date().toISOString().slice(0, 10);\n  321 |     await expect(page.locator('#pay-date')).toHaveValue(today);\n  322 |   });\n    at /var/www/360lm/tests/upi_pay.spec.js:319:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:22:55.579Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-today-date-is-always-pre-filled-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-today-date-is-always-pre-filled-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-6bd62d09f4f25f283fd0",
              "file": "upi_pay.spec.js",
              "line": 316,
              "column": 3
            },
            {
              "title": "to=shambhu param auto-selects Shambhu as payee",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 892,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6238,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:327:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 327
                        },
                        "snippet": "  325 |     await injectSession(page, SESSION.pramod);\n  326 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=5000');\n> 327 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  328 |     // Payee card should appear with Shambhu's name\n  329 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n  330 |     await expect(page.locator('#pay-amount')).toHaveValue('5000');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 327
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  325 |     await injectSession(page, SESSION.pramod);\n  326 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=5000');\n> 327 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  328 |     // Payee card should appear with Shambhu's name\n  329 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n  330 |     await expect(page.locator('#pay-amount')).toHaveValue('5000');\n    at /var/www/360lm/tests/upi_pay.spec.js:327:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:23:26.775Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-630f6-to-selects-Shambhu-as-payee-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-630f6-to-selects-Shambhu-as-payee-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 327
                      }
                    },
                    {
                      "workerIndex": 893,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6277,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:327:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 327
                        },
                        "snippet": "  325 |     await injectSession(page, SESSION.pramod);\n  326 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=5000');\n> 327 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  328 |     // Payee card should appear with Shambhu's name\n  329 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n  330 |     await expect(page.locator('#pay-amount')).toHaveValue('5000');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 327
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  325 |     await injectSession(page, SESSION.pramod);\n  326 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=5000');\n> 327 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  328 |     // Payee card should appear with Shambhu's name\n  329 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n  330 |     await expect(page.locator('#pay-amount')).toHaveValue('5000');\n    at /var/www/360lm/tests/upi_pay.spec.js:327:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:23:33.998Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-630f6-to-selects-Shambhu-as-payee-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-630f6-to-selects-Shambhu-as-payee-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 327
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-ccad07a02a249e0ca2f8",
              "file": "upi_pay.spec.js",
              "line": 324,
              "column": 3
            },
            {
              "title": "context=custodian-transfer pre-selects reason and opens pay screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1896,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6292,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:310:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 310
                        },
                        "snippet": "  308 |     await injectSession(page, SESSION.pramod);\n  309 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=30000');\n> 310 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  311 |     await expect(page.locator('#reason-chips .chip', { hasText: 'Custodian Transfer' })).toHaveClass(/sel/);\n  312 |     await expect(page.locator('#pay-amount')).toHaveValue('30000');\n  313 |     await expect(page.locator('#reason-notice')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 310
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  308 |     await injectSession(page, SESSION.pramod);\n  309 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=30000');\n> 310 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  311 |     await expect(page.locator('#reason-chips .chip', { hasText: 'Custodian Transfer' })).toHaveClass(/sel/);\n  312 |     await expect(page.locator('#pay-amount')).toHaveValue('30000');\n  313 |     await expect(page.locator('#reason-notice')).toBeVisible();\n    at /var/www/360lm/tests/upi_pay.spec.js:310:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:22:44.566Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-76834-reason-and-opens-pay-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-76834-reason-and-opens-pay-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 310
                      }
                    },
                    {
                      "workerIndex": 1897,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6296,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:310:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 310
                        },
                        "snippet": "  308 |     await injectSession(page, SESSION.pramod);\n  309 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=30000');\n> 310 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  311 |     await expect(page.locator('#reason-chips .chip', { hasText: 'Custodian Transfer' })).toHaveClass(/sel/);\n  312 |     await expect(page.locator('#pay-amount')).toHaveValue('30000');\n  313 |     await expect(page.locator('#reason-notice')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 310
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  308 |     await injectSession(page, SESSION.pramod);\n  309 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=30000');\n> 310 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  311 |     await expect(page.locator('#reason-chips .chip', { hasText: 'Custodian Transfer' })).toHaveClass(/sel/);\n  312 |     await expect(page.locator('#pay-amount')).toHaveValue('30000');\n  313 |     await expect(page.locator('#reason-notice')).toBeVisible();\n    at /var/www/360lm/tests/upi_pay.spec.js:310:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:22:51.830Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-76834-reason-and-opens-pay-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-76834-reason-and-opens-pay-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 310
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-26185018f05cdee69376",
              "file": "upi_pay.spec.js",
              "line": 307,
              "column": 3
            },
            {
              "title": "today date is always pre-filled",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1898,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30094,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 319
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  317 |     await injectSession(page, SESSION.pramod);\n  318 |     await page.goto(BASE_URL);\n> 319 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  320 |     const today = new Date().toISOString().slice(0, 10);\n  321 |     await expect(page.locator('#pay-date')).toHaveValue(today);\n  322 |   });\n    at /var/www/360lm/tests/upi_pay.spec.js:319:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:22:59.076Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-today-date-is-always-pre-filled-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-today-date-is-always-pre-filled-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1899,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30127,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 319
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  317 |     await injectSession(page, SESSION.pramod);\n  318 |     await page.goto(BASE_URL);\n> 319 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  320 |     const today = new Date().toISOString().slice(0, 10);\n  321 |     await expect(page.locator('#pay-date')).toHaveValue(today);\n  322 |   });\n    at /var/www/360lm/tests/upi_pay.spec.js:319:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:23:30.186Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-today-date-is-always-pre-filled-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-today-date-is-always-pre-filled-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-08016e8d004ac18e03bd",
              "file": "upi_pay.spec.js",
              "line": 316,
              "column": 3
            },
            {
              "title": "to=shambhu param auto-selects Shambhu as payee",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1900,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6446,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:327:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 327
                        },
                        "snippet": "  325 |     await injectSession(page, SESSION.pramod);\n  326 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=5000');\n> 327 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  328 |     // Payee card should appear with Shambhu's name\n  329 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n  330 |     await expect(page.locator('#pay-amount')).toHaveValue('5000');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 327
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  325 |     await injectSession(page, SESSION.pramod);\n  326 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=5000');\n> 327 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  328 |     // Payee card should appear with Shambhu's name\n  329 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n  330 |     await expect(page.locator('#pay-amount')).toHaveValue('5000');\n    at /var/www/360lm/tests/upi_pay.spec.js:327:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:24:02.109Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-630f6-to-selects-Shambhu-as-payee-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-630f6-to-selects-Shambhu-as-payee-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 327
                      }
                    },
                    {
                      "workerIndex": 1901,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6258,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:327:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 327
                        },
                        "snippet": "  325 |     await injectSession(page, SESSION.pramod);\n  326 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=5000');\n> 327 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  328 |     // Payee card should appear with Shambhu's name\n  329 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n  330 |     await expect(page.locator('#pay-amount')).toHaveValue('5000');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 327
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  325 |     await injectSession(page, SESSION.pramod);\n  326 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=5000');\n> 327 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  328 |     // Payee card should appear with Shambhu's name\n  329 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n  330 |     await expect(page.locator('#pay-amount')).toHaveValue('5000');\n    at /var/www/360lm/tests/upi_pay.spec.js:327:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:24:09.764Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-630f6-to-selects-Shambhu-as-payee-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-URL-Param-Pre-fill-630f6-to-selects-Shambhu-as-payee-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 327
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-7f7576589dc331cd7c68",
              "file": "upi_pay.spec.js",
              "line": 324,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4 — Integration",
          "file": "upi_pay.spec.js",
          "line": 337,
          "column": 6,
          "specs": [
            {
              "title": "advance payment: submit success shows toast and stays in UPI Pay",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 894,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30089,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 356
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  354 |     await injectSession(page, SESSION.pramod);\n  355 |     await page.goto(BASE_URL);\n> 356 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  357 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  358 |\n  359 |     // Select payee\n    at /var/www/360lm/tests/upi_pay.spec.js:356:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:23:41.314Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-f68e3--toast-and-stays-in-UPI-Pay-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-f68e3--toast-and-stays-in-UPI-Pay-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 895,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30086,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 356
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  354 |     await injectSession(page, SESSION.pramod);\n  355 |     await page.goto(BASE_URL);\n> 356 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  357 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  358 |\n  359 |     // Select payee\n    at /var/www/360lm/tests/upi_pay.spec.js:356:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:24:12.429Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-f68e3--toast-and-stays-in-UPI-Pay-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-f68e3--toast-and-stays-in-UPI-Pay-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-6352cf51c767fcebc77e",
              "file": "upi_pay.spec.js",
              "line": 339,
              "column": 3
            },
            {
              "title": "custodian-transfer via URL params: submit redirects to Custodian",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 896,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6252,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:384:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 384
                        },
                        "snippet": "  382 |     await injectSession(page, SESSION.pramod);\n  383 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=3000');\n> 384 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  385 |\n  386 |     // Wait for payee pre-fill\n  387 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 384
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  382 |     await injectSession(page, SESSION.pramod);\n  383 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=3000');\n> 384 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  385 |\n  386 |     // Wait for payee pre-fill\n  387 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n    at /var/www/360lm/tests/upi_pay.spec.js:384:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:24:43.494Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-8c226-bmit-redirects-to-Custodian-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-8c226-bmit-redirects-to-Custodian-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 384
                      }
                    },
                    {
                      "workerIndex": 897,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6247,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:384:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 384
                        },
                        "snippet": "  382 |     await injectSession(page, SESSION.pramod);\n  383 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=3000');\n> 384 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  385 |\n  386 |     // Wait for payee pre-fill\n  387 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 384
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  382 |     await injectSession(page, SESSION.pramod);\n  383 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=3000');\n> 384 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  385 |\n  386 |     // Wait for payee pre-fill\n  387 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n    at /var/www/360lm/tests/upi_pay.spec.js:384:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:24:50.689Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-8c226-bmit-redirects-to-Custodian-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-8c226-bmit-redirects-to-Custodian-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 384
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-d6f28e68ecaa89a758b5",
              "file": "upi_pay.spec.js",
              "line": 378,
              "column": 3
            },
            {
              "title": "advance payment: submit success shows toast and stays in UPI Pay",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1902,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30091,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 356
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  354 |     await injectSession(page, SESSION.pramod);\n  355 |     await page.goto(BASE_URL);\n> 356 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  357 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  358 |\n  359 |     // Select payee\n    at /var/www/360lm/tests/upi_pay.spec.js:356:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:24:16.965Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-f68e3--toast-and-stays-in-UPI-Pay-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-f68e3--toast-and-stays-in-UPI-Pay-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1903,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30068,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 36,
                            "line": 356
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('.btn-cta')\u001b[22m\n\n\n  354 |     await injectSession(page, SESSION.pramod);\n  355 |     await page.goto(BASE_URL);\n> 356 |     await page.locator('.btn-cta').click();\n      |                                    ^\n  357 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 3000 });\n  358 |\n  359 |     // Select payee\n    at /var/www/360lm/tests/upi_pay.spec.js:356:36"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:24:48.059Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-f68e3--toast-and-stays-in-UPI-Pay-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-f68e3--toast-and-stays-in-UPI-Pay-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-21db536ee9186265ea47",
              "file": "upi_pay.spec.js",
              "line": 339,
              "column": 3
            },
            {
              "title": "custodian-transfer via URL params: submit redirects to Custodian",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1904,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6266,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:384:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 384
                        },
                        "snippet": "  382 |     await injectSession(page, SESSION.pramod);\n  383 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=3000');\n> 384 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  385 |\n  386 |     // Wait for payee pre-fill\n  387 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 384
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  382 |     await injectSession(page, SESSION.pramod);\n  383 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=3000');\n> 384 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  385 |\n  386 |     // Wait for payee pre-fill\n  387 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n    at /var/www/360lm/tests/upi_pay.spec.js:384:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:25:19.054Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-8c226-bmit-redirects-to-Custodian-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-8c226-bmit-redirects-to-Custodian-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 384
                      }
                    },
                    {
                      "workerIndex": 1905,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6256,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n    at /var/www/360lm/tests/upi_pay.spec.js:384:42",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 42,
                          "line": 384
                        },
                        "snippet": "  382 |     await injectSession(page, SESSION.pramod);\n  383 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=3000');\n> 384 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  385 |\n  386 |     // Wait for payee pre-fill\n  387 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 42,
                            "line": 384
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoHaveClass\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-pay')\nExpected pattern: \u001b[32m/active/\u001b[39m\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toHaveClass\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-pay')\u001b[22m\n\n\n  382 |     await injectSession(page, SESSION.pramod);\n  383 |     await page.goto(BASE_URL + '?context=custodian-transfer&to=shambhu&amount=3000');\n> 384 |     await expect(page.locator('#s-pay')).toHaveClass(/active/, { timeout: 6000 });\n      |                                          ^\n  385 |\n  386 |     // Wait for payee pre-fill\n  387 |     await expect(page.locator('#payee-card .payee-sel-name')).toContainText('Shambhu', { timeout: 5000 });\n    at /var/www/360lm/tests/upi_pay.spec.js:384:42"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:25:26.296Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-8c226-bmit-redirects-to-Custodian-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Integrat-8c226-bmit-redirects-to-Custodian-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 42,
                        "line": 384
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-9a72fd1883f633bf43ac",
              "file": "upi_pay.spec.js",
              "line": 378,
              "column": 3
            }
          ]
        },
        {
          "title": "Phase 4 — Custodian → UPI Pay Button",
          "file": "upi_pay.spec.js",
          "line": 408,
          "column": 6,
          "specs": [
            {
              "title": "UPI Pay button hidden by default (cash mode)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 898,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:24:57.909Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-d3e1f-idden-by-default-cash-mode--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-d3e1f-idden-by-default-cash-mode--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    },
                    {
                      "workerIndex": 899,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30071,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:25:28.912Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-d3e1f-idden-by-default-cash-mode--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-d3e1f-idden-by-default-cash-mode--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-ac545c77b591bc29e8cf",
              "file": "upi_pay.spec.js",
              "line": 419,
              "column": 3
            },
            {
              "title": "UPI Pay button appears when UPI mode selected",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 900,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:25:59.946Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-b563c-ears-when-UPI-mode-selected-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-b563c-ears-when-UPI-mode-selected-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    },
                    {
                      "workerIndex": 901,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30069,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:26:30.964Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-b563c-ears-when-UPI-mode-selected-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-b563c-ears-when-UPI-mode-selected-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-abae9cba37b37043fe1b",
              "file": "upi_pay.spec.js",
              "line": 423,
              "column": 3
            },
            {
              "title": "UPI Pay button hidden again when switching to NEFT",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 902,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30103,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:27:02.202Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-ed96a-gain-when-switching-to-NEFT-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-ed96a-gain-when-switching-to-NEFT-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    },
                    {
                      "workerIndex": 903,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30109,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:27:33.314Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-ed96a-gain-when-switching-to-NEFT-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-ed96a-gain-when-switching-to-NEFT-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-e933fed7542d66579e18",
              "file": "upi_pay.spec.js",
              "line": 428,
              "column": 3
            },
            {
              "title": "UPI Pay button navigates to /finance/upi/ with correct params",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 904,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30068,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:28:04.919Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-8d594-nce-upi-with-correct-params-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-8d594-nce-upi-with-correct-params-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    },
                    {
                      "workerIndex": 905,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30083,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:28:36.035Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-8d594-nce-upi-with-correct-params-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-8d594-nce-upi-with-correct-params-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-e23194d3e33479ec077f",
              "file": "upi_pay.spec.js",
              "line": 435,
              "column": 3
            },
            {
              "title": "UPI Pay button hidden by default (cash mode)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1906,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30113,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:25:33.485Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-d3e1f-idden-by-default-cash-mode--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-d3e1f-idden-by-default-cash-mode--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    },
                    {
                      "workerIndex": 1907,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30077,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:26:04.635Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-d3e1f-idden-by-default-cash-mode--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-d3e1f-idden-by-default-cash-mode--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-2d30c83683e41f8dd0b0",
              "file": "upi_pay.spec.js",
              "line": 419,
              "column": 3
            },
            {
              "title": "UPI Pay button appears when UPI mode selected",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1908,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30101,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:26:35.618Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-b563c-ears-when-UPI-mode-selected-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-b563c-ears-when-UPI-mode-selected-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    },
                    {
                      "workerIndex": 1909,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30074,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:27:06.747Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-b563c-ears-when-UPI-mode-selected-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-b563c-ears-when-UPI-mode-selected-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-04c00d6e369f41b54c11",
              "file": "upi_pay.spec.js",
              "line": 423,
              "column": 3
            },
            {
              "title": "UPI Pay button hidden again when switching to NEFT",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1910,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30094,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:27:37.837Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-ed96a-gain-when-switching-to-NEFT-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-ed96a-gain-when-switching-to-NEFT-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    },
                    {
                      "workerIndex": 1911,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30098,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:28:09.008Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-ed96a-gain-when-switching-to-NEFT-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-ed96a-gain-when-switching-to-NEFT-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-c854d4c1185031218672",
              "file": "upi_pay.spec.js",
              "line": 428,
              "column": 3
            },
            {
              "title": "UPI Pay button navigates to /finance/upi/ with correct params",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1912,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30067,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:28:40.077Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-8d594-nce-upi-with-correct-params-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-8d594-nce-upi-with-correct-params-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    },
                    {
                      "workerIndex": 1913,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30093,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8",
                        "location": {
                          "file": "/var/www/360lm/tests/upi_pay.spec.js",
                          "column": 8,
                          "line": 409
                        },
                        "snippet": "  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 8,
                            "line": 409
                          },
                          "message": "\u001b[31mTest timeout of 30000ms exceeded while running \"beforeEach\" hook.\u001b[39m\n\n  407 |\n  408 | test.describe('Phase 4 — Custodian → UPI Pay Button', () => {\n> 409 |   test.beforeEach(async ({ page }) => {\n      |        ^\n  410 |     await page.addInitScript((s) => {\n  411 |       localStorage.setItem('lm360-session', JSON.stringify(s));\n  412 |     }, SESSION.pramod);\n    at /var/www/360lm/tests/upi_pay.spec.js:409:8"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/upi_pay.spec.js",
                            "column": 41,
                            "line": 415
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#nav-transfer')\u001b[22m\n\n\n  413 |     await page.goto(CUST_URL);\n  414 |     // Go to Transfer screen\n> 415 |     await page.locator('#nav-transfer').click();\n      |                                         ^\n  416 |     await expect(page.locator('#s-transfer')).toHaveClass(/active/, { timeout: 4000 });\n  417 |   });\n  418 |\n    at /var/www/360lm/tests/upi_pay.spec.js:415:41"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:29:11.148Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-8d594-nce-upi-with-correct-params-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/upi_pay-Phase-4-—-Custodia-8d594-nce-upi-with-correct-params-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/upi_pay.spec.js",
                        "column": 8,
                        "line": 409
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "72325c8b005ff943e1bd-e8f86cca9f5cd5bd71f7",
              "file": "upi_pay.spec.js",
              "line": 435,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "vehicle.spec.js",
      "file": "vehicle.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Vehicle Log — Smoke",
          "file": "vehicle.spec.js",
          "line": 9,
          "column": 6,
          "specs": [
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1631,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:07.047Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-c69be7604bfc5bd3eecf",
              "file": "vehicle.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 182,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:08.836Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-0af762c479f72f5c2368",
              "file": "vehicle.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js contains current vehicle version",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 199,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:09.048Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-4c6f0c5164abc99757d6",
              "file": "vehicle.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1524,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:42.240Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-f19cecc0003599b5d468",
              "file": "vehicle.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 195,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:43.935Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-6d3329d2335f2eb6f7a3",
              "file": "vehicle.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js contains current vehicle version",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 194,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:44.160Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-b4f1974d25bcf1fbe34d",
              "file": "vehicle.spec.js",
              "line": 22,
              "column": 3
            }
          ]
        },
        {
          "title": "Vehicle Log — HTML Structure (v18)",
          "file": "vehicle.spec.js",
          "line": 29,
          "column": 6,
          "specs": [
            {
              "title": "edit+delegate modals and edit-log screen present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 65,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:09.274Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-61ff6582f0f0b6314fd2",
              "file": "vehicle.spec.js",
              "line": 30,
              "column": 3
            },
            {
              "title": "canBypassPhoto and canEditEntry functions present in JS",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 23,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:09.356Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-e3848d43d66a83c9c8de",
              "file": "vehicle.spec.js",
              "line": 50,
              "column": 3
            },
            {
              "title": "photo bypass guards present (4 locations)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:09.392Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-9b6dda5ff14592fbe0ed",
              "file": "vehicle.spec.js",
              "line": 64,
              "column": 3
            },
            {
              "title": "edit+delegate modals and edit-log screen present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 66,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:44.374Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-eecfce8c5bbdcfc30b12",
              "file": "vehicle.spec.js",
              "line": 30,
              "column": 3
            },
            {
              "title": "canBypassPhoto and canEditEntry functions present in JS",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 28,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:44.453Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-1da678b2208dbba484d5",
              "file": "vehicle.spec.js",
              "line": 50,
              "column": 3
            },
            {
              "title": "photo bypass guards present (4 locations)",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 20,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:44.493Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-d807e8ea32c68934afb9",
              "file": "vehicle.spec.js",
              "line": 64,
              "column": 3
            }
          ]
        },
        {
          "title": "Vehicle Log — HTML Structure (v22)",
          "file": "vehicle.spec.js",
          "line": 76,
          "column": 6,
          "specs": [
            {
              "title": "mileage segment functions and retro-switch present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 13,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:09.420Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-e81fe7704c34b9bba4d8",
              "file": "vehicle.spec.js",
              "line": 77,
              "column": 3
            },
            {
              "title": "mileage segment functions and retro-switch present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 15,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:44.525Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-4ad5358a0f338a97379f",
              "file": "vehicle.spec.js",
              "line": 77,
              "column": 3
            }
          ]
        },
        {
          "title": "Vehicle Log — HTML Structure (v25)",
          "file": "vehicle.spec.js",
          "line": 93,
          "column": 6,
          "specs": [
            {
              "title": "fuel bill photo and CC slip photo elements present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 18,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:09.445Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-4ccd65b60ead57af1fd6",
              "file": "vehicle.spec.js",
              "line": 94,
              "column": 3
            },
            {
              "title": "fuel bill and CC slip handler functions present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:09.475Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-6be3e9e521885024edcc",
              "file": "vehicle.spec.js",
              "line": 108,
              "column": 3
            },
            {
              "title": "fuel bill photo and CC slip photo elements present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 18,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:44.553Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-ce93d65c65ac5105f143",
              "file": "vehicle.spec.js",
              "line": 94,
              "column": 3
            },
            {
              "title": "fuel bill and CC slip handler functions present",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 17,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:44.581Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-c8df628ad737ecf34fd7",
              "file": "vehicle.spec.js",
              "line": 108,
              "column": 3
            }
          ]
        },
        {
          "title": "Vehicle Log — Hub Navigation",
          "file": "vehicle.spec.js",
          "line": 122,
          "column": 6,
          "specs": [
            {
              "title": "dashboard header has Hub home button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 11,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:09.501Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-c65046058d915619ea54",
              "file": "vehicle.spec.js",
              "line": 123,
              "column": 3
            },
            {
              "title": "sw.js version is v30 or later",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:09.521Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-6f06cad994967ee91b8e",
              "file": "vehicle.spec.js",
              "line": 132,
              "column": 3
            },
            {
              "title": "dashboard header has Hub home button",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 14,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:44.610Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-f4a712a3829d53a9aed5",
              "file": "vehicle.spec.js",
              "line": 123,
              "column": 3
            },
            {
              "title": "sw.js version is v30 or later",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 10,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:44.638Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "084e7535606ab6e45f08-a6266891ef4a68c85905",
              "file": "vehicle.spec.js",
              "line": 132,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "vendors.spec.js",
      "file": "vendors.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Vendor Management — Smoke",
          "file": "vendors.spec.js",
          "line": 34,
          "column": 6,
          "specs": [
            {
              "title": "page returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 906,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 174,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:37:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 28,
                          "line": 37
                        },
                        "snippet": "  35 |   test('page returns 200', async ({ page }) => {\n  36 |     const resp = await page.goto(BASE_URL);\n> 37 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  38 |   });\n  39 |\n  40 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 28,
                            "line": 37
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  35 |   test('page returns 200', async ({ page }) => {\n  36 |     const resp = await page.goto(BASE_URL);\n> 37 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  38 |   });\n  39 |\n  40 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:37:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:09.567Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-page-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-page-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 28,
                        "line": 37
                      }
                    },
                    {
                      "workerIndex": 907,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 287,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:37:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 28,
                          "line": 37
                        },
                        "snippet": "  35 |   test('page returns 200', async ({ page }) => {\n  36 |     const resp = await page.goto(BASE_URL);\n> 37 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  38 |   });\n  39 |\n  40 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 28,
                            "line": 37
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  35 |   test('page returns 200', async ({ page }) => {\n  36 |     const resp = await page.goto(BASE_URL);\n> 37 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  38 |   });\n  39 |\n  40 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:37:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:29:10.573Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-page-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-page-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 28,
                        "line": 37
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-193b2a3dd0e787dbb7c5",
              "file": "vendors.spec.js",
              "line": 35,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 908,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 269,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:42:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 28,
                          "line": 42
                        },
                        "snippet": "  40 |   test('manifest.json returns 200', async ({ page }) => {\n  41 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 42 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  43 |   });\n  44 |\n  45 |   test('HTML contains current vendors SW version', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 28,
                            "line": 42
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  40 |   test('manifest.json returns 200', async ({ page }) => {\n  41 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 42 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  43 |   });\n  44 |\n  45 |   test('HTML contains current vendors SW version', async () => {\n    at /var/www/360lm/tests/vendors.spec.js:42:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:11.856Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 28,
                        "line": 42
                      }
                    },
                    {
                      "workerIndex": 909,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 285,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:42:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 28,
                          "line": 42
                        },
                        "snippet": "  40 |   test('manifest.json returns 200', async ({ page }) => {\n  41 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 42 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  43 |   });\n  44 |\n  45 |   test('HTML contains current vendors SW version', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 28,
                            "line": 42
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  40 |   test('manifest.json returns 200', async ({ page }) => {\n  41 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 42 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  43 |   });\n  44 |\n  45 |   test('HTML contains current vendors SW version', async () => {\n    at /var/www/360lm/tests/vendors.spec.js:42:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:29:13.052Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 28,
                        "line": 42
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-9d9820802d55bcb44528",
              "file": "vendors.spec.js",
              "line": 40,
              "column": 3
            },
            {
              "title": "HTML contains current vendors SW version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 910,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 55,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:49:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 49
                        },
                        "snippet": "  47 |     const resp = await ctx.get(BASE_URL);\n  48 |     const html = await resp.text();\n> 49 |     expect(html).toMatch(/vendors-v\\d+/);\n     |                  ^\n  50 |     expect(html).toContain('Vendor Management');\n  51 |     await ctx.dispose();\n  52 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  47 |     const resp = await ctx.get(BASE_URL);\n  48 |     const html = await resp.text();\n> 49 |     expect(html).toMatch(/vendors-v\\d+/);\n     |                  ^\n  50 |     expect(html).toContain('Vendor Management');\n  51 |     await ctx.dispose();\n  52 |   });\n    at /var/www/360lm/tests/vendors.spec.js:49:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:14.289Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3d891--current-vendors-SW-version-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 49
                      }
                    },
                    {
                      "workerIndex": 911,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:49:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 49
                        },
                        "snippet": "  47 |     const resp = await ctx.get(BASE_URL);\n  48 |     const html = await resp.text();\n> 49 |     expect(html).toMatch(/vendors-v\\d+/);\n     |                  ^\n  50 |     expect(html).toContain('Vendor Management');\n  51 |     await ctx.dispose();\n  52 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  47 |     const resp = await ctx.get(BASE_URL);\n  48 |     const html = await resp.text();\n> 49 |     expect(html).toMatch(/vendors-v\\d+/);\n     |                  ^\n  50 |     expect(html).toContain('Vendor Management');\n  51 |     await ctx.dispose();\n  52 |   });\n    at /var/www/360lm/tests/vendors.spec.js:49:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:29:15.119Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3d891--current-vendors-SW-version-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 49
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-58eb8f734621c5bd9545",
              "file": "vendors.spec.js",
              "line": 45,
              "column": 3
            },
            {
              "title": "page returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1914,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 193,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:37:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 28,
                          "line": 37
                        },
                        "snippet": "  35 |   test('page returns 200', async ({ page }) => {\n  36 |     const resp = await page.goto(BASE_URL);\n> 37 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  38 |   });\n  39 |\n  40 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 28,
                            "line": 37
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  35 |   test('page returns 200', async ({ page }) => {\n  36 |     const resp = await page.goto(BASE_URL);\n> 37 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  38 |   });\n  39 |\n  40 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:37:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:44.688Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-page-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-page-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 28,
                        "line": 37
                      }
                    },
                    {
                      "workerIndex": 1915,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 287,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:37:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 28,
                          "line": 37
                        },
                        "snippet": "  35 |   test('page returns 200', async ({ page }) => {\n  36 |     const resp = await page.goto(BASE_URL);\n> 37 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  38 |   });\n  39 |\n  40 |   test('manifest.json returns 200', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 28,
                            "line": 37
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  35 |   test('page returns 200', async ({ page }) => {\n  36 |     const resp = await page.goto(BASE_URL);\n> 37 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  38 |   });\n  39 |\n  40 |   test('manifest.json returns 200', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:37:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:29:45.761Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-page-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-page-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 28,
                        "line": 37
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-63f80140d86ae1556d33",
              "file": "vendors.spec.js",
              "line": 35,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1916,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 300,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:42:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 28,
                          "line": 42
                        },
                        "snippet": "  40 |   test('manifest.json returns 200', async ({ page }) => {\n  41 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 42 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  43 |   });\n  44 |\n  45 |   test('HTML contains current vendors SW version', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 28,
                            "line": 42
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  40 |   test('manifest.json returns 200', async ({ page }) => {\n  41 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 42 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  43 |   });\n  44 |\n  45 |   test('HTML contains current vendors SW version', async () => {\n    at /var/www/360lm/tests/vendors.spec.js:42:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:47.031Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 28,
                        "line": 42
                      }
                    },
                    {
                      "workerIndex": 1917,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 323,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:42:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 28,
                          "line": 42
                        },
                        "snippet": "  40 |   test('manifest.json returns 200', async ({ page }) => {\n  41 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 42 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  43 |   });\n  44 |\n  45 |   test('HTML contains current vendors SW version', async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 28,
                            "line": 42
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  40 |   test('manifest.json returns 200', async ({ page }) => {\n  41 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 42 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  43 |   });\n  44 |\n  45 |   test('HTML contains current vendors SW version', async () => {\n    at /var/www/360lm/tests/vendors.spec.js:42:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:29:48.459Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 28,
                        "line": 42
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-42280e3e4aff83528e73",
              "file": "vendors.spec.js",
              "line": 40,
              "column": 3
            },
            {
              "title": "HTML contains current vendors SW version",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1918,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 61,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:49:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 49
                        },
                        "snippet": "  47 |     const resp = await ctx.get(BASE_URL);\n  48 |     const html = await resp.text();\n> 49 |     expect(html).toMatch(/vendors-v\\d+/);\n     |                  ^\n  50 |     expect(html).toContain('Vendor Management');\n  51 |     await ctx.dispose();\n  52 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  47 |     const resp = await ctx.get(BASE_URL);\n  48 |     const html = await resp.text();\n> 49 |     expect(html).toMatch(/vendors-v\\d+/);\n     |                  ^\n  50 |     expect(html).toContain('Vendor Management');\n  51 |     await ctx.dispose();\n  52 |   });\n    at /var/www/360lm/tests/vendors.spec.js:49:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:49.786Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3d891--current-vendors-SW-version-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 49
                      }
                    },
                    {
                      "workerIndex": 1919,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:49:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 49
                        },
                        "snippet": "  47 |     const resp = await ctx.get(BASE_URL);\n  48 |     const html = await resp.text();\n> 49 |     expect(html).toMatch(/vendors-v\\d+/);\n     |                  ^\n  50 |     expect(html).toContain('Vendor Management');\n  51 |     await ctx.dispose();\n  52 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 49
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoMatch\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m\n\nExpected pattern: \u001b[32m/vendors-v\\d+/\u001b[39m\nReceived string:  \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  47 |     const resp = await ctx.get(BASE_URL);\n  48 |     const html = await resp.text();\n> 49 |     expect(html).toMatch(/vendors-v\\d+/);\n     |                  ^\n  50 |     expect(html).toContain('Vendor Management');\n  51 |     await ctx.dispose();\n  52 |   });\n    at /var/www/360lm/tests/vendors.spec.js:49:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:29:50.713Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3d891--current-vendors-SW-version-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 49
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-bfeffcd02313c45051db",
              "file": "vendors.spec.js",
              "line": 45,
              "column": 3
            }
          ]
        },
        {
          "title": "Vendor Management — HTML Structure",
          "file": "vendors.spec.js",
          "line": 56,
          "column": 6,
          "specs": [
            {
              "title": "all four screens present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 912,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:61:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 61
                        },
                        "snippet": "  59 |     const resp = await ctx.get(BASE_URL);\n  60 |     const html = await resp.text();\n> 61 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  62 |     expect(html).toContain('id=\"s-detail\"');\n  63 |     expect(html).toContain('id=\"s-form\"');\n  64 |     expect(html).toContain('id=\"t-total\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  59 |     const resp = await ctx.get(BASE_URL);\n  60 |     const html = await resp.text();\n> 61 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  62 |     expect(html).toContain('id=\"s-detail\"');\n  63 |     expect(html).toContain('id=\"s-form\"');\n  64 |     expect(html).toContain('id=\"t-total\"');\n    at /var/www/360lm/tests/vendors.spec.js:61:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:15.949Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e86c0-re-all-four-screens-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 61
                      }
                    },
                    {
                      "workerIndex": 913,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 52,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:61:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 61
                        },
                        "snippet": "  59 |     const resp = await ctx.get(BASE_URL);\n  60 |     const html = await resp.text();\n> 61 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  62 |     expect(html).toContain('id=\"s-detail\"');\n  63 |     expect(html).toContain('id=\"s-form\"');\n  64 |     expect(html).toContain('id=\"t-total\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  59 |     const resp = await ctx.get(BASE_URL);\n  60 |     const html = await resp.text();\n> 61 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  62 |     expect(html).toContain('id=\"s-detail\"');\n  63 |     expect(html).toContain('id=\"s-form\"');\n  64 |     expect(html).toContain('id=\"t-total\"');\n    at /var/www/360lm/tests/vendors.spec.js:61:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:29:16.830Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e86c0-re-all-four-screens-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 61
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-8ac8586963adec460654",
              "file": "vendors.spec.js",
              "line": 57,
              "column": 3
            },
            {
              "title": "bottom nav has 2 tabs (List, Add)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 914,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 71,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:74:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 74
                        },
                        "snippet": "  72 |     const resp = await ctx.get(BASE_URL);\n  73 |     const html = await resp.text();\n> 74 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  75 |     expect(html).toContain('id=\"nav-add\"');\n  76 |     await ctx.dispose();\n  77 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 74
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  72 |     const resp = await ctx.get(BASE_URL);\n  73 |     const html = await resp.text();\n> 74 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  75 |     expect(html).toContain('id=\"nav-add\"');\n  76 |     await ctx.dispose();\n  77 |   });\n    at /var/www/360lm/tests/vendors.spec.js:74:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:17.687Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--acf1c-om-nav-has-2-tabs-List-Add--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 74
                      }
                    },
                    {
                      "workerIndex": 915,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:74:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 74
                        },
                        "snippet": "  72 |     const resp = await ctx.get(BASE_URL);\n  73 |     const html = await resp.text();\n> 74 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  75 |     expect(html).toContain('id=\"nav-add\"');\n  76 |     await ctx.dispose();\n  77 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 74
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  72 |     const resp = await ctx.get(BASE_URL);\n  73 |     const html = await resp.text();\n> 74 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  75 |     expect(html).toContain('id=\"nav-add\"');\n  76 |     await ctx.dispose();\n  77 |   });\n    at /var/www/360lm/tests/vendors.spec.js:74:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:29:18.594Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--acf1c-om-nav-has-2-tabs-List-Add--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 74
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-db44f9071fc3e105764d",
              "file": "vendors.spec.js",
              "line": 70,
              "column": 3
            },
            {
              "title": "all 11 form fields present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 916,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/vendors.spec.js:85:20)\n    at /var/www/360lm/tests/vendors.spec.js:84:59",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 20,
                          "line": 85
                        },
                        "snippet": "  83 |     ['f-name','f-type','f-proprietor','f-upi-receipt','f-phone','f-email',\n  84 |      'f-gst','f-pan','f-contact','f-address','f-aliases'].forEach(id => {\n> 85 |       expect(html).toContain(`id=\"${id}\"`);\n     |                    ^\n  86 |     });\n  87 |     await ctx.dispose();\n  88 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 20,
                            "line": 85
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  83 |     ['f-name','f-type','f-proprietor','f-upi-receipt','f-phone','f-email',\n  84 |      'f-gst','f-pan','f-contact','f-address','f-aliases'].forEach(id => {\n> 85 |       expect(html).toContain(`id=\"${id}\"`);\n     |                    ^\n  86 |     });\n  87 |     await ctx.dispose();\n  88 |   });\n    at forEach (/var/www/360lm/tests/vendors.spec.js:85:20)\n    at /var/www/360lm/tests/vendors.spec.js:84:59"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:19.460Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ec853--all-11-form-fields-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 20,
                        "line": 85
                      }
                    },
                    {
                      "workerIndex": 917,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/vendors.spec.js:85:20)\n    at /var/www/360lm/tests/vendors.spec.js:84:59",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 20,
                          "line": 85
                        },
                        "snippet": "  83 |     ['f-name','f-type','f-proprietor','f-upi-receipt','f-phone','f-email',\n  84 |      'f-gst','f-pan','f-contact','f-address','f-aliases'].forEach(id => {\n> 85 |       expect(html).toContain(`id=\"${id}\"`);\n     |                    ^\n  86 |     });\n  87 |     await ctx.dispose();\n  88 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 20,
                            "line": 85
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  83 |     ['f-name','f-type','f-proprietor','f-upi-receipt','f-phone','f-email',\n  84 |      'f-gst','f-pan','f-contact','f-address','f-aliases'].forEach(id => {\n> 85 |       expect(html).toContain(`id=\"${id}\"`);\n     |                    ^\n  86 |     });\n  87 |     await ctx.dispose();\n  88 |   });\n    at forEach (/var/www/360lm/tests/vendors.spec.js:85:20)\n    at /var/www/360lm/tests/vendors.spec.js:84:59"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:29:20.376Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ec853--all-11-form-fields-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 20,
                        "line": 85
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-d88279e0a7152952f72d",
              "file": "vendors.spec.js",
              "line": 79,
              "column": 3
            },
            {
              "title": "search bar + type filter chips present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 918,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 59,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:94:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 94
                        },
                        "snippet": "  92 |     const resp = await ctx.get(BASE_URL);\n  93 |     const html = await resp.text();\n> 94 |     expect(html).toContain('id=\"search-q\"');\n     |                  ^\n  95 |     expect(html).toContain('Search across name, proprietor, phone, GST, PAN');\n  96 |     expect(html).toContain('id=\"type-chips\"');\n  97 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 94
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  92 |     const resp = await ctx.get(BASE_URL);\n  93 |     const html = await resp.text();\n> 94 |     expect(html).toContain('id=\"search-q\"');\n     |                  ^\n  95 |     expect(html).toContain('Search across name, proprietor, phone, GST, PAN');\n  96 |     expect(html).toContain('id=\"type-chips\"');\n  97 |     await ctx.dispose();\n    at /var/www/360lm/tests/vendors.spec.js:94:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:21.320Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4d7be-r-type-filter-chips-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 94
                      }
                    },
                    {
                      "workerIndex": 919,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 54,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:94:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 94
                        },
                        "snippet": "  92 |     const resp = await ctx.get(BASE_URL);\n  93 |     const html = await resp.text();\n> 94 |     expect(html).toContain('id=\"search-q\"');\n     |                  ^\n  95 |     expect(html).toContain('Search across name, proprietor, phone, GST, PAN');\n  96 |     expect(html).toContain('id=\"type-chips\"');\n  97 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 94
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  92 |     const resp = await ctx.get(BASE_URL);\n  93 |     const html = await resp.text();\n> 94 |     expect(html).toContain('id=\"search-q\"');\n     |                  ^\n  95 |     expect(html).toContain('Search across name, proprietor, phone, GST, PAN');\n  96 |     expect(html).toContain('id=\"type-chips\"');\n  97 |     await ctx.dispose();\n    at /var/www/360lm/tests/vendors.spec.js:94:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:29:22.191Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4d7be-r-type-filter-chips-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 94
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-a8aedba8bcb93095faf4",
              "file": "vendors.spec.js",
              "line": 90,
              "column": 3
            },
            {
              "title": "ledger panel + 3 summary tiles present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 920,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:104:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 104
                        },
                        "snippet": "  102 |     const resp = await ctx.get(BASE_URL);\n  103 |     const html = await resp.text();\n> 104 |     expect(html).toContain('id=\"l-out\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"l-in\"');\n  106 |     expect(html).toContain('id=\"l-net\"');\n  107 |     expect(html).toContain('id=\"l-list\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 104
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  102 |     const resp = await ctx.get(BASE_URL);\n  103 |     const html = await resp.text();\n> 104 |     expect(html).toContain('id=\"l-out\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"l-in\"');\n  106 |     expect(html).toContain('id=\"l-net\"');\n  107 |     expect(html).toContain('id=\"l-list\"');\n    at /var/www/360lm/tests/vendors.spec.js:104:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:23.054Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--02f50-nel-3-summary-tiles-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 104
                      }
                    },
                    {
                      "workerIndex": 921,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 51,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:104:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 104
                        },
                        "snippet": "  102 |     const resp = await ctx.get(BASE_URL);\n  103 |     const html = await resp.text();\n> 104 |     expect(html).toContain('id=\"l-out\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"l-in\"');\n  106 |     expect(html).toContain('id=\"l-net\"');\n  107 |     expect(html).toContain('id=\"l-list\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 104
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  102 |     const resp = await ctx.get(BASE_URL);\n  103 |     const html = await resp.text();\n> 104 |     expect(html).toContain('id=\"l-out\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"l-in\"');\n  106 |     expect(html).toContain('id=\"l-net\"');\n  107 |     expect(html).toContain('id=\"l-list\"');\n    at /var/www/360lm/tests/vendors.spec.js:104:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:29:23.879Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--02f50-nel-3-summary-tiles-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 104
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-8a66d084be9db47fdaf4",
              "file": "vendors.spec.js",
              "line": 100,
              "column": 3
            },
            {
              "title": "all four screens present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1920,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:61:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 61
                        },
                        "snippet": "  59 |     const resp = await ctx.get(BASE_URL);\n  60 |     const html = await resp.text();\n> 61 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  62 |     expect(html).toContain('id=\"s-detail\"');\n  63 |     expect(html).toContain('id=\"s-form\"');\n  64 |     expect(html).toContain('id=\"t-total\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  59 |     const resp = await ctx.get(BASE_URL);\n  60 |     const html = await resp.text();\n> 61 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  62 |     expect(html).toContain('id=\"s-detail\"');\n  63 |     expect(html).toContain('id=\"s-form\"');\n  64 |     expect(html).toContain('id=\"t-total\"');\n    at /var/www/360lm/tests/vendors.spec.js:61:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:51.637Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e86c0-re-all-four-screens-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 61
                      }
                    },
                    {
                      "workerIndex": 1921,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:61:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 61
                        },
                        "snippet": "  59 |     const resp = await ctx.get(BASE_URL);\n  60 |     const html = await resp.text();\n> 61 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  62 |     expect(html).toContain('id=\"s-detail\"');\n  63 |     expect(html).toContain('id=\"s-form\"');\n  64 |     expect(html).toContain('id=\"t-total\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 61
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"s-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  59 |     const resp = await ctx.get(BASE_URL);\n  60 |     const html = await resp.text();\n> 61 |     expect(html).toContain('id=\"s-list\"');\n     |                  ^\n  62 |     expect(html).toContain('id=\"s-detail\"');\n  63 |     expect(html).toContain('id=\"s-form\"');\n  64 |     expect(html).toContain('id=\"t-total\"');\n    at /var/www/360lm/tests/vendors.spec.js:61:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:29:52.610Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e86c0-re-all-four-screens-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 61
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-2da3b4afcecafa5261fe",
              "file": "vendors.spec.js",
              "line": 57,
              "column": 3
            },
            {
              "title": "bottom nav has 2 tabs (List, Add)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1922,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:74:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 74
                        },
                        "snippet": "  72 |     const resp = await ctx.get(BASE_URL);\n  73 |     const html = await resp.text();\n> 74 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  75 |     expect(html).toContain('id=\"nav-add\"');\n  76 |     await ctx.dispose();\n  77 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 74
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  72 |     const resp = await ctx.get(BASE_URL);\n  73 |     const html = await resp.text();\n> 74 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  75 |     expect(html).toContain('id=\"nav-add\"');\n  76 |     await ctx.dispose();\n  77 |   });\n    at /var/www/360lm/tests/vendors.spec.js:74:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:53.538Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--acf1c-om-nav-has-2-tabs-List-Add--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 74
                      }
                    },
                    {
                      "workerIndex": 1923,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:74:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 74
                        },
                        "snippet": "  72 |     const resp = await ctx.get(BASE_URL);\n  73 |     const html = await resp.text();\n> 74 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  75 |     expect(html).toContain('id=\"nav-add\"');\n  76 |     await ctx.dispose();\n  77 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 74
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"nav-list\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  72 |     const resp = await ctx.get(BASE_URL);\n  73 |     const html = await resp.text();\n> 74 |     expect(html).toContain('id=\"nav-list\"');\n     |                  ^\n  75 |     expect(html).toContain('id=\"nav-add\"');\n  76 |     await ctx.dispose();\n  77 |   });\n    at /var/www/360lm/tests/vendors.spec.js:74:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:29:54.500Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--acf1c-om-nav-has-2-tabs-List-Add--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 74
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-c5b44391489be405dc11",
              "file": "vendors.spec.js",
              "line": 70,
              "column": 3
            },
            {
              "title": "all 11 form fields present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1924,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/vendors.spec.js:85:20)\n    at /var/www/360lm/tests/vendors.spec.js:84:59",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 20,
                          "line": 85
                        },
                        "snippet": "  83 |     ['f-name','f-type','f-proprietor','f-upi-receipt','f-phone','f-email',\n  84 |      'f-gst','f-pan','f-contact','f-address','f-aliases'].forEach(id => {\n> 85 |       expect(html).toContain(`id=\"${id}\"`);\n     |                    ^\n  86 |     });\n  87 |     await ctx.dispose();\n  88 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 20,
                            "line": 85
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  83 |     ['f-name','f-type','f-proprietor','f-upi-receipt','f-phone','f-email',\n  84 |      'f-gst','f-pan','f-contact','f-address','f-aliases'].forEach(id => {\n> 85 |       expect(html).toContain(`id=\"${id}\"`);\n     |                    ^\n  86 |     });\n  87 |     await ctx.dispose();\n  88 |   });\n    at forEach (/var/www/360lm/tests/vendors.spec.js:85:20)\n    at /var/www/360lm/tests/vendors.spec.js:84:59"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:55.458Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ec853--all-11-form-fields-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 20,
                        "line": 85
                      }
                    },
                    {
                      "workerIndex": 1925,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 70,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at forEach (/var/www/360lm/tests/vendors.spec.js:85:20)\n    at /var/www/360lm/tests/vendors.spec.js:84:59",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 20,
                          "line": 85
                        },
                        "snippet": "  83 |     ['f-name','f-type','f-proprietor','f-upi-receipt','f-phone','f-email',\n  84 |      'f-gst','f-pan','f-contact','f-address','f-aliases'].forEach(id => {\n> 85 |       expect(html).toContain(`id=\"${id}\"`);\n     |                    ^\n  86 |     });\n  87 |     await ctx.dispose();\n  88 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 20,
                            "line": 85
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"f-name\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  83 |     ['f-name','f-type','f-proprietor','f-upi-receipt','f-phone','f-email',\n  84 |      'f-gst','f-pan','f-contact','f-address','f-aliases'].forEach(id => {\n> 85 |       expect(html).toContain(`id=\"${id}\"`);\n     |                    ^\n  86 |     });\n  87 |     await ctx.dispose();\n  88 |   });\n    at forEach (/var/www/360lm/tests/vendors.spec.js:85:20)\n    at /var/www/360lm/tests/vendors.spec.js:84:59"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:29:56.499Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ec853--all-11-form-fields-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 20,
                        "line": 85
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-bbf8fd2359408e999076",
              "file": "vendors.spec.js",
              "line": 79,
              "column": 3
            },
            {
              "title": "search bar + type filter chips present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1926,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 57,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:94:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 94
                        },
                        "snippet": "  92 |     const resp = await ctx.get(BASE_URL);\n  93 |     const html = await resp.text();\n> 94 |     expect(html).toContain('id=\"search-q\"');\n     |                  ^\n  95 |     expect(html).toContain('Search across name, proprietor, phone, GST, PAN');\n  96 |     expect(html).toContain('id=\"type-chips\"');\n  97 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 94
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  92 |     const resp = await ctx.get(BASE_URL);\n  93 |     const html = await resp.text();\n> 94 |     expect(html).toContain('id=\"search-q\"');\n     |                  ^\n  95 |     expect(html).toContain('Search across name, proprietor, phone, GST, PAN');\n  96 |     expect(html).toContain('id=\"type-chips\"');\n  97 |     await ctx.dispose();\n    at /var/www/360lm/tests/vendors.spec.js:94:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:57.420Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4d7be-r-type-filter-chips-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 94
                      }
                    },
                    {
                      "workerIndex": 1927,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:94:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 94
                        },
                        "snippet": "  92 |     const resp = await ctx.get(BASE_URL);\n  93 |     const html = await resp.text();\n> 94 |     expect(html).toContain('id=\"search-q\"');\n     |                  ^\n  95 |     expect(html).toContain('Search across name, proprietor, phone, GST, PAN');\n  96 |     expect(html).toContain('id=\"type-chips\"');\n  97 |     await ctx.dispose();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 94
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"search-q\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  92 |     const resp = await ctx.get(BASE_URL);\n  93 |     const html = await resp.text();\n> 94 |     expect(html).toContain('id=\"search-q\"');\n     |                  ^\n  95 |     expect(html).toContain('Search across name, proprietor, phone, GST, PAN');\n  96 |     expect(html).toContain('id=\"type-chips\"');\n  97 |     await ctx.dispose();\n    at /var/www/360lm/tests/vendors.spec.js:94:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:29:58.423Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4d7be-r-type-filter-chips-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 94
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-310880881778918bb178",
              "file": "vendors.spec.js",
              "line": 90,
              "column": 3
            },
            {
              "title": "ledger panel + 3 summary tiles present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1928,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 56,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:104:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 104
                        },
                        "snippet": "  102 |     const resp = await ctx.get(BASE_URL);\n  103 |     const html = await resp.text();\n> 104 |     expect(html).toContain('id=\"l-out\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"l-in\"');\n  106 |     expect(html).toContain('id=\"l-net\"');\n  107 |     expect(html).toContain('id=\"l-list\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 104
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  102 |     const resp = await ctx.get(BASE_URL);\n  103 |     const html = await resp.text();\n> 104 |     expect(html).toContain('id=\"l-out\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"l-in\"');\n  106 |     expect(html).toContain('id=\"l-net\"');\n  107 |     expect(html).toContain('id=\"l-list\"');\n    at /var/www/360lm/tests/vendors.spec.js:104:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:29:59.294Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--02f50-nel-3-summary-tiles-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 104
                      }
                    },
                    {
                      "workerIndex": 1929,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 62,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:104:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 104
                        },
                        "snippet": "  102 |     const resp = await ctx.get(BASE_URL);\n  103 |     const html = await resp.text();\n> 104 |     expect(html).toContain('id=\"l-out\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"l-in\"');\n  106 |     expect(html).toContain('id=\"l-net\"');\n  107 |     expect(html).toContain('id=\"l-list\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 104
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"l-out\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  102 |     const resp = await ctx.get(BASE_URL);\n  103 |     const html = await resp.text();\n> 104 |     expect(html).toContain('id=\"l-out\"');\n      |                  ^\n  105 |     expect(html).toContain('id=\"l-in\"');\n  106 |     expect(html).toContain('id=\"l-net\"');\n  107 |     expect(html).toContain('id=\"l-list\"');\n    at /var/www/360lm/tests/vendors.spec.js:104:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:30:00.261Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--02f50-nel-3-summary-tiles-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 104
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-4cd8f685e19ff7594b65",
              "file": "vendors.spec.js",
              "line": 100,
              "column": 3
            }
          ]
        },
        {
          "title": "Vendor Management — Auth & Access",
          "file": "vendors.spec.js",
          "line": 114,
          "column": 6,
          "specs": [
            {
              "title": "no session → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 922,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6253,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n    at /var/www/360lm/tests/vendors.spec.js:117:16",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 16,
                          "line": 117
                        },
                        "snippet": "  115 |   test('no session → redirects to /hub/', async ({ page }) => {\n  116 |     await page.goto(BASE_URL);\n> 117 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  118 |     expect(page.url()).toContain('/hub/');\n  119 |   });\n  120 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 16,
                            "line": 117
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n\n  115 |   test('no session → redirects to /hub/', async ({ page }) => {\n  116 |     await page.goto(BASE_URL);\n> 117 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  118 |     expect(page.url()).toContain('/hub/');\n  119 |   });\n  120 |\n    at /var/www/360lm/tests/vendors.spec.js:117:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:24.718Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--48658-session-→-redirects-to-hub--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--48658-session-→-redirects-to-hub--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 16,
                        "line": 117
                      }
                    },
                    {
                      "workerIndex": 923,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6314,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n    at /var/www/360lm/tests/vendors.spec.js:117:16",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 16,
                          "line": 117
                        },
                        "snippet": "  115 |   test('no session → redirects to /hub/', async ({ page }) => {\n  116 |     await page.goto(BASE_URL);\n> 117 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  118 |     expect(page.url()).toContain('/hub/');\n  119 |   });\n  120 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 16,
                            "line": 117
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n\n  115 |   test('no session → redirects to /hub/', async ({ page }) => {\n  116 |     await page.goto(BASE_URL);\n> 117 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  118 |     expect(page.url()).toContain('/hub/');\n  119 |   });\n  120 |\n    at /var/www/360lm/tests/vendors.spec.js:117:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:29:31.964Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--48658-session-→-redirects-to-hub--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--48658-session-→-redirects-to-hub--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 16,
                        "line": 117
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-df484aa998f3b8637dd2",
              "file": "vendors.spec.js",
              "line": 115,
              "column": 3
            },
            {
              "title": "expired session (>12h) → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 924,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6264,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n    at /var/www/360lm/tests/vendors.spec.js:128:16",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 16,
                          "line": 128
                        },
                        "snippet": "  126 |     });\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  129 |     expect(page.url()).toContain('/hub/');\n  130 |   });\n  131 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 16,
                            "line": 128
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n\n  126 |     });\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  129 |     expect(page.url()).toContain('/hub/');\n  130 |   });\n  131 |\n    at /var/www/360lm/tests/vendors.spec.js:128:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:39.291Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--125b2-ion-12h-→-redirects-to-hub--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--125b2-ion-12h-→-redirects-to-hub--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 16,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 925,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6294,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n    at /var/www/360lm/tests/vendors.spec.js:128:16",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 16,
                          "line": 128
                        },
                        "snippet": "  126 |     });\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  129 |     expect(page.url()).toContain('/hub/');\n  130 |   });\n  131 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 16,
                            "line": 128
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n\n  126 |     });\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  129 |     expect(page.url()).toContain('/hub/');\n  130 |   });\n  131 |\n    at /var/www/360lm/tests/vendors.spec.js:128:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:29:46.524Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--125b2-ion-12h-→-redirects-to-hub--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--125b2-ion-12h-→-redirects-to-hub--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 16,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-0445338163da2b143705",
              "file": "vendors.spec.js",
              "line": 121,
              "column": 3
            },
            {
              "title": "non-admin without grant → Access Restricted screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 926,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6291,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:135:58",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 58,
                          "line": 135
                        },
                        "snippet": "  133 |     await injectSession(page, 'mukesh', 'Mukesh');\n  134 |     await page.goto(BASE_URL);\n> 135 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n      |                                                          ^\n  136 |   });\n  137 |\n  138 |   test('Harish (admin) → list loads with tiles', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 58,
                            "line": 135
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  133 |     await injectSession(page, 'mukesh', 'Mukesh');\n  134 |     await page.goto(BASE_URL);\n> 135 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n      |                                                          ^\n  136 |   });\n  137 |\n  138 |   test('Harish (admin) → list loads with tiles', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:135:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:29:53.810Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--06beb--→-Access-Restricted-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--06beb--→-Access-Restricted-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 58,
                        "line": 135
                      }
                    },
                    {
                      "workerIndex": 927,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6469,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:135:58",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 58,
                          "line": 135
                        },
                        "snippet": "  133 |     await injectSession(page, 'mukesh', 'Mukesh');\n  134 |     await page.goto(BASE_URL);\n> 135 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n      |                                                          ^\n  136 |   });\n  137 |\n  138 |   test('Harish (admin) → list loads with tiles', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 58,
                            "line": 135
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  133 |     await injectSession(page, 'mukesh', 'Mukesh');\n  134 |     await page.goto(BASE_URL);\n> 135 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n      |                                                          ^\n  136 |   });\n  137 |\n  138 |   test('Harish (admin) → list loads with tiles', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:135:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:30:01.631Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--06beb--→-Access-Restricted-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--06beb--→-Access-Restricted-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 58,
                        "line": 135
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-84c2cb5491a04e408e5b",
              "file": "vendors.spec.js",
              "line": 132,
              "column": 3
            },
            {
              "title": "Harish (admin) → list loads with tiles",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 928,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8280,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:141:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 141
                        },
                        "snippet": "  139 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  140 |     await page.goto(BASE_URL);\n> 141 |     await expect(page.locator('#s-list.active')).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  142 |     // Tiles should populate with numbers\n  143 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n  144 |     await expect(page.locator('#t-vendor')).not.toHaveText('—');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 141
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n\n\n  139 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  140 |     await page.goto(BASE_URL);\n> 141 |     await expect(page.locator('#s-list.active')).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  142 |     // Tiles should populate with numbers\n  143 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n  144 |     await expect(page.locator('#t-vendor')).not.toHaveText('—');\n    at /var/www/360lm/tests/vendors.spec.js:141:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:30:09.550Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--8d682-min-→-list-loads-with-tiles-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--8d682-min-→-list-loads-with-tiles-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 141
                      }
                    },
                    {
                      "workerIndex": 929,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8283,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:141:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 141
                        },
                        "snippet": "  139 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  140 |     await page.goto(BASE_URL);\n> 141 |     await expect(page.locator('#s-list.active')).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  142 |     // Tiles should populate with numbers\n  143 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n  144 |     await expect(page.locator('#t-vendor')).not.toHaveText('—');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 141
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n\n\n  139 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  140 |     await page.goto(BASE_URL);\n> 141 |     await expect(page.locator('#s-list.active')).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  142 |     // Tiles should populate with numbers\n  143 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n  144 |     await expect(page.locator('#t-vendor')).not.toHaveText('—');\n    at /var/www/360lm/tests/vendors.spec.js:141:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:30:18.850Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--8d682-min-→-list-loads-with-tiles-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--8d682-min-→-list-loads-with-tiles-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 141
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-0f00accf563323dbcd69",
              "file": "vendors.spec.js",
              "line": 138,
              "column": 3
            },
            {
              "title": "Pramod (admin) → list loads",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 930,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8276,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:150:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 150
                        },
                        "snippet": "  148 |     await injectSession(page, 'pramod', 'Pramod Narang');\n  149 |     await page.goto(BASE_URL);\n> 150 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  151 |   });\n  152 |\n  153 |   test('user pill shows logged-in name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 150
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  148 |     await injectSession(page, 'pramod', 'Pramod Narang');\n  149 |     await page.goto(BASE_URL);\n> 150 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  151 |   });\n  152 |\n  153 |   test('user pill shows logged-in name', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:150:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:30:28.173Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--0d703-s-Pramod-admin-→-list-loads-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--0d703-s-Pramod-admin-→-list-loads-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 150
                      }
                    },
                    {
                      "workerIndex": 931,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8264,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:150:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 150
                        },
                        "snippet": "  148 |     await injectSession(page, 'pramod', 'Pramod Narang');\n  149 |     await page.goto(BASE_URL);\n> 150 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  151 |   });\n  152 |\n  153 |   test('user pill shows logged-in name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 150
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  148 |     await injectSession(page, 'pramod', 'Pramod Narang');\n  149 |     await page.goto(BASE_URL);\n> 150 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  151 |   });\n  152 |\n  153 |   test('user pill shows logged-in name', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:150:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:30:37.680Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--0d703-s-Pramod-admin-→-list-loads-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--0d703-s-Pramod-admin-→-list-loads-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 150
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-62700f0986a7ca64625f",
              "file": "vendors.spec.js",
              "line": 147,
              "column": 3
            },
            {
              "title": "user pill shows logged-in name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 932,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8257,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:156:46",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 46,
                          "line": 156
                        },
                        "snippet": "  154 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  155 |     await page.goto(BASE_URL);\n> 156 |     await expect(page.locator('#user-pill')).toContainText('Harish', { timeout: 8000 });\n      |                                              ^\n  157 |   });\n  158 | });\n  159 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 46,
                            "line": 156
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n\n\n  154 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  155 |     await page.goto(BASE_URL);\n> 156 |     await expect(page.locator('#user-pill')).toContainText('Harish', { timeout: 8000 });\n      |                                              ^\n  157 |   });\n  158 | });\n  159 |\n    at /var/www/360lm/tests/vendors.spec.js:156:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:30:46.985Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--01b19-r-pill-shows-logged-in-name-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--01b19-r-pill-shows-logged-in-name-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 46,
                        "line": 156
                      }
                    },
                    {
                      "workerIndex": 933,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8293,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:156:46",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 46,
                          "line": 156
                        },
                        "snippet": "  154 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  155 |     await page.goto(BASE_URL);\n> 156 |     await expect(page.locator('#user-pill')).toContainText('Harish', { timeout: 8000 });\n      |                                              ^\n  157 |   });\n  158 | });\n  159 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 46,
                            "line": 156
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n\n\n  154 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  155 |     await page.goto(BASE_URL);\n> 156 |     await expect(page.locator('#user-pill')).toContainText('Harish', { timeout: 8000 });\n      |                                              ^\n  157 |   });\n  158 | });\n  159 |\n    at /var/www/360lm/tests/vendors.spec.js:156:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:30:56.379Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--01b19-r-pill-shows-logged-in-name-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--01b19-r-pill-shows-logged-in-name-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 46,
                        "line": 156
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-1146c6f08002b0a8c6bb",
              "file": "vendors.spec.js",
              "line": 153,
              "column": 3
            },
            {
              "title": "no session → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1930,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6616,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n    at /var/www/360lm/tests/vendors.spec.js:117:16",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 16,
                          "line": 117
                        },
                        "snippet": "  115 |   test('no session → redirects to /hub/', async ({ page }) => {\n  116 |     await page.goto(BASE_URL);\n> 117 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  118 |     expect(page.url()).toContain('/hub/');\n  119 |   });\n  120 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 16,
                            "line": 117
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n\n  115 |   test('no session → redirects to /hub/', async ({ page }) => {\n  116 |     await page.goto(BASE_URL);\n> 117 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  118 |     expect(page.url()).toContain('/hub/');\n  119 |   });\n  120 |\n    at /var/www/360lm/tests/vendors.spec.js:117:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:30:01.272Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--48658-session-→-redirects-to-hub--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--48658-session-→-redirects-to-hub--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 16,
                        "line": 117
                      }
                    },
                    {
                      "workerIndex": 1931,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6526,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n    at /var/www/360lm/tests/vendors.spec.js:117:16",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 16,
                          "line": 117
                        },
                        "snippet": "  115 |   test('no session → redirects to /hub/', async ({ page }) => {\n  116 |     await page.goto(BASE_URL);\n> 117 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  118 |     expect(page.url()).toContain('/hub/');\n  119 |   });\n  120 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 16,
                            "line": 117
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n\n  115 |   test('no session → redirects to /hub/', async ({ page }) => {\n  116 |     await page.goto(BASE_URL);\n> 117 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  118 |     expect(page.url()).toContain('/hub/');\n  119 |   });\n  120 |\n    at /var/www/360lm/tests/vendors.spec.js:117:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:30:11.193Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--48658-session-→-redirects-to-hub--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--48658-session-→-redirects-to-hub--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 16,
                        "line": 117
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-0dcf4f9737148962899b",
              "file": "vendors.spec.js",
              "line": 115,
              "column": 3
            },
            {
              "title": "expired session (>12h) → redirects to /hub/",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1932,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6351,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n    at /var/www/360lm/tests/vendors.spec.js:128:16",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 16,
                          "line": 128
                        },
                        "snippet": "  126 |     });\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  129 |     expect(page.url()).toContain('/hub/');\n  130 |   });\n  131 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 16,
                            "line": 128
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n\n  126 |     });\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  129 |     expect(page.url()).toContain('/hub/');\n  130 |   });\n  131 |\n    at /var/www/360lm/tests/vendors.spec.js:128:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:30:19.010Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--125b2-ion-12h-→-redirects-to-hub--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--125b2-ion-12h-→-redirects-to-hub--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 16,
                        "line": 128
                      }
                    },
                    {
                      "workerIndex": 1933,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6315,
                      "error": {
                        "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================",
                        "stack": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n    at /var/www/360lm/tests/vendors.spec.js:128:16",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 16,
                          "line": 128
                        },
                        "snippet": "  126 |     });\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  129 |     expect(page.url()).toContain('/hub/');\n  130 |   });\n  131 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 16,
                            "line": 128
                          },
                          "message": "TimeoutError: page.waitForURL: Timeout 6000ms exceeded.\n=========================== logs ===========================\nwaiting for navigation until \"load\"\n============================================================\n\n  126 |     });\n  127 |     await page.goto(BASE_URL);\n> 128 |     await page.waitForURL(/\\/hub\\//, { timeout: 6000 });\n      |                ^\n  129 |     expect(page.url()).toContain('/hub/');\n  130 |   });\n  131 |\n    at /var/www/360lm/tests/vendors.spec.js:128:16"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:30:26.689Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--125b2-ion-12h-→-redirects-to-hub--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--125b2-ion-12h-→-redirects-to-hub--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 16,
                        "line": 128
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-0db1dd9aa57c3fcd4285",
              "file": "vendors.spec.js",
              "line": 121,
              "column": 3
            },
            {
              "title": "non-admin without grant → Access Restricted screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1934,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6306,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:135:58",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 58,
                          "line": 135
                        },
                        "snippet": "  133 |     await injectSession(page, 'mukesh', 'Mukesh');\n  134 |     await page.goto(BASE_URL);\n> 135 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n      |                                                          ^\n  136 |   });\n  137 |\n  138 |   test('Harish (admin) → list loads with tiles', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 58,
                            "line": 135
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  133 |     await injectSession(page, 'mukesh', 'Mukesh');\n  134 |     await page.goto(BASE_URL);\n> 135 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n      |                                                          ^\n  136 |   });\n  137 |\n  138 |   test('Harish (admin) → list loads with tiles', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:135:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:30:34.188Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--06beb--→-Access-Restricted-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--06beb--→-Access-Restricted-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 58,
                        "line": 135
                      }
                    },
                    {
                      "workerIndex": 1935,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 6313,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:135:58",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 58,
                          "line": 135
                        },
                        "snippet": "  133 |     await injectSession(page, 'mukesh', 'Mukesh');\n  134 |     await page.goto(BASE_URL);\n> 135 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n      |                                                          ^\n  136 |   });\n  137 |\n  138 |   test('Harish (admin) → list loads with tiles', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 58,
                            "line": 135
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('text=Access Restricted')\nExpected: visible\nTimeout: 6000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 6000ms\u001b[22m\n\u001b[2m  - waiting for locator('text=Access Restricted')\u001b[22m\n\n\n  133 |     await injectSession(page, 'mukesh', 'Mukesh');\n  134 |     await page.goto(BASE_URL);\n> 135 |     await expect(page.locator('text=Access Restricted')).toBeVisible({ timeout: 6000 });\n      |                                                          ^\n  136 |   });\n  137 |\n  138 |   test('Harish (admin) → list loads with tiles', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:135:58"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:30:41.638Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--06beb--→-Access-Restricted-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--06beb--→-Access-Restricted-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 58,
                        "line": 135
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-57c44a2475cf2023c11f",
              "file": "vendors.spec.js",
              "line": 132,
              "column": 3
            },
            {
              "title": "Harish (admin) → list loads with tiles",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1936,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8241,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:141:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 141
                        },
                        "snippet": "  139 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  140 |     await page.goto(BASE_URL);\n> 141 |     await expect(page.locator('#s-list.active')).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  142 |     // Tiles should populate with numbers\n  143 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n  144 |     await expect(page.locator('#t-vendor')).not.toHaveText('—');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 141
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n\n\n  139 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  140 |     await page.goto(BASE_URL);\n> 141 |     await expect(page.locator('#s-list.active')).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  142 |     // Tiles should populate with numbers\n  143 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n  144 |     await expect(page.locator('#t-vendor')).not.toHaveText('—');\n    at /var/www/360lm/tests/vendors.spec.js:141:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:30:48.991Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--8d682-min-→-list-loads-with-tiles-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--8d682-min-→-list-loads-with-tiles-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 141
                      }
                    },
                    {
                      "workerIndex": 1937,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8268,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:141:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 141
                        },
                        "snippet": "  139 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  140 |     await page.goto(BASE_URL);\n> 141 |     await expect(page.locator('#s-list.active')).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  142 |     // Tiles should populate with numbers\n  143 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n  144 |     await expect(page.locator('#t-vendor')).not.toHaveText('—');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 141
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#s-list.active')\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#s-list.active')\u001b[22m\n\n\n  139 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  140 |     await page.goto(BASE_URL);\n> 141 |     await expect(page.locator('#s-list.active')).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  142 |     // Tiles should populate with numbers\n  143 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n  144 |     await expect(page.locator('#t-vendor')).not.toHaveText('—');\n    at /var/www/360lm/tests/vendors.spec.js:141:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:30:58.243Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--8d682-min-→-list-loads-with-tiles-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--8d682-min-→-list-loads-with-tiles-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 141
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-667c04dc900e06fc3faa",
              "file": "vendors.spec.js",
              "line": 138,
              "column": 3
            },
            {
              "title": "Pramod (admin) → list loads",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1938,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8221,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:150:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 150
                        },
                        "snippet": "  148 |     await injectSession(page, 'pramod', 'Pramod Narang');\n  149 |     await page.goto(BASE_URL);\n> 150 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  151 |   });\n  152 |\n  153 |   test('user pill shows logged-in name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 150
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  148 |     await injectSession(page, 'pramod', 'Pramod Narang');\n  149 |     await page.goto(BASE_URL);\n> 150 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  151 |   });\n  152 |\n  153 |   test('user pill shows logged-in name', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:150:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:31:07.591Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--0d703-s-Pramod-admin-→-list-loads-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--0d703-s-Pramod-admin-→-list-loads-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 150
                      }
                    },
                    {
                      "workerIndex": 1939,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8259,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:150:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 150
                        },
                        "snippet": "  148 |     await injectSession(page, 'pramod', 'Pramod Narang');\n  149 |     await page.goto(BASE_URL);\n> 150 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  151 |   });\n  152 |\n  153 |   test('user pill shows logged-in name', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 150
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  148 |     await injectSession(page, 'pramod', 'Pramod Narang');\n  149 |     await page.goto(BASE_URL);\n> 150 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  151 |   });\n  152 |\n  153 |   test('user pill shows logged-in name', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:150:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:31:16.826Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--0d703-s-Pramod-admin-→-list-loads-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--0d703-s-Pramod-admin-→-list-loads-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 150
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-b7c44d4214ab4b173893",
              "file": "vendors.spec.js",
              "line": 147,
              "column": 3
            },
            {
              "title": "user pill shows logged-in name",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1940,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8251,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:156:46",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 46,
                          "line": 156
                        },
                        "snippet": "  154 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  155 |     await page.goto(BASE_URL);\n> 156 |     await expect(page.locator('#user-pill')).toContainText('Harish', { timeout: 8000 });\n      |                                              ^\n  157 |   });\n  158 | });\n  159 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 46,
                            "line": 156
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n\n\n  154 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  155 |     await page.goto(BASE_URL);\n> 156 |     await expect(page.locator('#user-pill')).toContainText('Harish', { timeout: 8000 });\n      |                                              ^\n  157 |   });\n  158 | });\n  159 |\n    at /var/www/360lm/tests/vendors.spec.js:156:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:31:26.064Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--01b19-r-pill-shows-logged-in-name-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--01b19-r-pill-shows-logged-in-name-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 46,
                        "line": 156
                      }
                    },
                    {
                      "workerIndex": 1941,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8229,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:156:46",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 46,
                          "line": 156
                        },
                        "snippet": "  154 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  155 |     await page.goto(BASE_URL);\n> 156 |     await expect(page.locator('#user-pill')).toContainText('Harish', { timeout: 8000 });\n      |                                              ^\n  157 |   });\n  158 | });\n  159 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 46,
                            "line": 156
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoContainText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#user-pill')\nExpected substring: \u001b[32m\"Harish\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toContainText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#user-pill')\u001b[22m\n\n\n  154 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  155 |     await page.goto(BASE_URL);\n> 156 |     await expect(page.locator('#user-pill')).toContainText('Harish', { timeout: 8000 });\n      |                                              ^\n  157 |   });\n  158 | });\n  159 |\n    at /var/www/360lm/tests/vendors.spec.js:156:46"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:31:35.286Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--01b19-r-pill-shows-logged-in-name-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--01b19-r-pill-shows-logged-in-name-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 46,
                        "line": 156
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-2b96db07680274ef3486",
              "file": "vendors.spec.js",
              "line": 153,
              "column": 3
            }
          ]
        },
        {
          "title": "Vendor Management — List + Search",
          "file": "vendors.spec.js",
          "line": 161,
          "column": 6,
          "specs": [
            {
              "title": "vendor rows render",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 934,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8265,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:31:05.852Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-List-Search-vendor-rows-render-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-List-Search-vendor-rows-render-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 935,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8260,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:31:15.141Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-List-Search-vendor-rows-render-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-List-Search-vendor-rows-render-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-12b0c7b3706c58b52ab2",
              "file": "vendors.spec.js",
              "line": 168,
              "column": 3
            },
            {
              "title": "type chip \"Family\" filters the list",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 936,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8248,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:31:24.410Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--32974-hip-Family-filters-the-list-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--32974-hip-Family-filters-the-list-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 937,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8289,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:31:33.596Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--32974-hip-Family-filters-the-list-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--32974-hip-Family-filters-the-list-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-2c51b987bd0cb6672f7c",
              "file": "vendors.spec.js",
              "line": 174,
              "column": 3
            },
            {
              "title": "type chip \"Vendors\" filters to vendor type",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 938,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8261,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:31:42.869Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--c3770-dors-filters-to-vendor-type-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--c3770-dors-filters-to-vendor-type-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 939,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8303,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:31:52.186Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--c3770-dors-filters-to-vendor-type-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--c3770-dors-filters-to-vendor-type-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-c00a9586d00b62ce9073",
              "file": "vendors.spec.js",
              "line": 180,
              "column": 3
            },
            {
              "title": "search by name filters rows",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 940,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8498,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:32:01.738Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--addf2-search-by-name-filters-rows-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--addf2-search-by-name-filters-rows-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 941,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8303,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:32:11.380Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--addf2-search-by-name-filters-rows-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--addf2-search-by-name-filters-rows-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-4249c515992470750334",
              "file": "vendors.spec.js",
              "line": 185,
              "column": 3
            },
            {
              "title": "search with no match shows empty state",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 942,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8226,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:32:20.712Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ba562--no-match-shows-empty-state-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ba562--no-match-shows-empty-state-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 943,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8257,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:32:29.973Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ba562--no-match-shows-empty-state-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ba562--no-match-shows-empty-state-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-9cb98a8df7ccbb04ae2e",
              "file": "vendors.spec.js",
              "line": 194,
              "column": 3
            },
            {
              "title": "clearing type chip back to All shows all rows",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 944,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8247,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:32:39.234Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--6469a--back-to-All-shows-all-rows-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--6469a--back-to-All-shows-all-rows-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 945,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8263,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:32:48.527Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--6469a--back-to-All-shows-all-rows-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--6469a--back-to-All-shows-all-rows-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-a5fd944bfde7d35fd04c",
              "file": "vendors.spec.js",
              "line": 200,
              "column": 3
            },
            {
              "title": "vendor rows render",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1942,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8256,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:31:44.437Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-List-Search-vendor-rows-render-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-List-Search-vendor-rows-render-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 1943,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8415,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:31:53.756Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-List-Search-vendor-rows-render-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-List-Search-vendor-rows-render-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-4b507ae28dce00a14aed",
              "file": "vendors.spec.js",
              "line": 168,
              "column": 3
            },
            {
              "title": "type chip \"Family\" filters the list",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1944,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8321,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:32:03.882Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--32974-hip-Family-filters-the-list-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--32974-hip-Family-filters-the-list-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 1945,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8311,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:32:13.387Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--32974-hip-Family-filters-the-list-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--32974-hip-Family-filters-the-list-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-e94ebefb3f3744fb22e9",
              "file": "vendors.spec.js",
              "line": 174,
              "column": 3
            },
            {
              "title": "type chip \"Vendors\" filters to vendor type",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1946,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8251,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:32:22.765Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--c3770-dors-filters-to-vendor-type-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--c3770-dors-filters-to-vendor-type-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 1947,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8260,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:32:32.080Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--c3770-dors-filters-to-vendor-type-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--c3770-dors-filters-to-vendor-type-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-ea8ca909ff7d1e15371b",
              "file": "vendors.spec.js",
              "line": 180,
              "column": 3
            },
            {
              "title": "search by name filters rows",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1948,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8258,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:32:41.404Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--addf2-search-by-name-filters-rows-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--addf2-search-by-name-filters-rows-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 1949,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8233,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:32:50.698Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--addf2-search-by-name-filters-rows-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--addf2-search-by-name-filters-rows-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-a116b1e8a24caf113d72",
              "file": "vendors.spec.js",
              "line": 185,
              "column": 3
            },
            {
              "title": "search with no match shows empty state",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1950,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8283,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:32:59.947Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ba562--no-match-shows-empty-state-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ba562--no-match-shows-empty-state-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 1951,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8232,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:33:09.232Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ba562--no-match-shows-empty-state-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ba562--no-match-shows-empty-state-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-7985233ea6f3ffc8bc8c",
              "file": "vendors.spec.js",
              "line": 194,
              "column": 3
            },
            {
              "title": "clearing type chip back to All shows all rows",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1952,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8220,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:33:18.457Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--6469a--back-to-All-shows-all-rows-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--6469a--back-to-All-shows-all-rows-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    },
                    {
                      "workerIndex": 1953,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8241,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:165:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 165
                        },
                        "snippet": "  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 165
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  163 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  164 |     await page.goto(BASE_URL);\n> 165 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  166 |   });\n  167 |\n  168 |   test('vendor rows render', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:165:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:33:27.709Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--6469a--back-to-All-shows-all-rows-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--6469a--back-to-All-shows-all-rows-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 165
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-531b5a698c9df1d89894",
              "file": "vendors.spec.js",
              "line": 200,
              "column": 3
            }
          ]
        },
        {
          "title": "Vendor Management — Detail",
          "file": "vendors.spec.js",
          "line": 212,
          "column": 6,
          "specs": [
            {
              "title": "tapping a row opens detail screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 946,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8270,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:32:57.933Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e25f5-g-a-row-opens-detail-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e25f5-g-a-row-opens-detail-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 947,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8258,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:33:07.168Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e25f5-g-a-row-opens-detail-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e25f5-g-a-row-opens-detail-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-c74d7fc5d4726ad8fa1f",
              "file": "vendors.spec.js",
              "line": 219,
              "column": 3
            },
            {
              "title": "Info tab is selected by default",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 948,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:33:16.353Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4f64d--tab-is-selected-by-default-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4f64d--tab-is-selected-by-default-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 949,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8228,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:33:25.534Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4f64d--tab-is-selected-by-default-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4f64d--tab-is-selected-by-default-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-1343c0aa12f92739a0b6",
              "file": "vendors.spec.js",
              "line": 225,
              "column": 3
            },
            {
              "title": "switching to Ledger tab loads it",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 950,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8300,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:33:34.940Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ad1a6-hing-to-Ledger-tab-loads-it-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ad1a6-hing-to-Ledger-tab-loads-it-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 951,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8226,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:33:44.219Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ad1a6-hing-to-Ledger-tab-loads-it-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ad1a6-hing-to-Ledger-tab-loads-it-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-daeed72411b1292b66cf",
              "file": "vendors.spec.js",
              "line": 231,
              "column": 3
            },
            {
              "title": "Back arrow returns to list",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 952,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8448,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:33:53.411Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ae0a4--Back-arrow-returns-to-list-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ae0a4--Back-arrow-returns-to-list-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 953,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8311,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:34:03.293Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ae0a4--Back-arrow-returns-to-list-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ae0a4--Back-arrow-returns-to-list-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-ccc80a83f7dbcfc1cf33",
              "file": "vendors.spec.js",
              "line": 240,
              "column": 3
            },
            {
              "title": "Edit button opens form pre-filled",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 954,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8251,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:34:12.709Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--bce08-utton-opens-form-pre-filled-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--bce08-utton-opens-form-pre-filled-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 955,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8241,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:34:21.916Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--bce08-utton-opens-form-pre-filled-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--bce08-utton-opens-form-pre-filled-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-4c2529209c72890a5fd6",
              "file": "vendors.spec.js",
              "line": 246,
              "column": 3
            },
            {
              "title": "tapping a row opens detail screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1954,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8262,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:33:36.971Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e25f5-g-a-row-opens-detail-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e25f5-g-a-row-opens-detail-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 1955,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8269,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:33:46.274Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e25f5-g-a-row-opens-detail-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--e25f5-g-a-row-opens-detail-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-1710c4baf930c3ed4fd9",
              "file": "vendors.spec.js",
              "line": 219,
              "column": 3
            },
            {
              "title": "Info tab is selected by default",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1956,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8271,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:33:55.572Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4f64d--tab-is-selected-by-default-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4f64d--tab-is-selected-by-default-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 1957,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8264,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:34:04.960Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4f64d--tab-is-selected-by-default-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4f64d--tab-is-selected-by-default-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-51d580316ba0c49a7e91",
              "file": "vendors.spec.js",
              "line": 225,
              "column": 3
            },
            {
              "title": "switching to Ledger tab loads it",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1958,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8228,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:34:14.242Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ad1a6-hing-to-Ledger-tab-loads-it-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ad1a6-hing-to-Ledger-tab-loads-it-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 1959,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8262,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:34:23.483Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ad1a6-hing-to-Ledger-tab-loads-it-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ad1a6-hing-to-Ledger-tab-loads-it-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-23944431f8cd3a475232",
              "file": "vendors.spec.js",
              "line": 231,
              "column": 3
            },
            {
              "title": "Back arrow returns to list",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1960,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8262,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:34:32.836Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ae0a4--Back-arrow-returns-to-list-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ae0a4--Back-arrow-returns-to-list-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 1961,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8253,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:34:42.183Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ae0a4--Back-arrow-returns-to-list-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--ae0a4--Back-arrow-returns-to-list-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-a0d7e8452278e338ea4a",
              "file": "vendors.spec.js",
              "line": 240,
              "column": 3
            },
            {
              "title": "Edit button opens form pre-filled",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1962,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8263,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:34:51.353Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--bce08-utton-opens-form-pre-filled-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--bce08-utton-opens-form-pre-filled-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    },
                    {
                      "workerIndex": 1963,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:216:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 216
                        },
                        "snippet": "  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 216
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  214 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  215 |     await page.goto(BASE_URL);\n> 216 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  217 |   });\n  218 |\n  219 |   test('tapping a row opens detail screen', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:216:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:35:00.646Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--bce08-utton-opens-form-pre-filled-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--bce08-utton-opens-form-pre-filled-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 216
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-7862ee08ef2ead08a5ec",
              "file": "vendors.spec.js",
              "line": 246,
              "column": 3
            }
          ]
        },
        {
          "title": "Vendor Management — Add / Edit / Delete",
          "file": "vendors.spec.js",
          "line": 256,
          "column": 6,
          "specs": [
            {
              "title": "Add tab opens empty form",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 956,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8253,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:34:31.196Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    },
                    {
                      "workerIndex": 957,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8237,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:34:40.522Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-ecabe2e1d8ce7dd53e15",
              "file": "vendors.spec.js",
              "line": 271,
              "column": 3
            },
            {
              "title": "saving with empty name shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 958,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8241,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:34:49.795Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    },
                    {
                      "workerIndex": 959,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8273,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:34:59.226Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-e7785de4f9993ab5c351",
              "file": "vendors.spec.js",
              "line": 278,
              "column": 3
            },
            {
              "title": "full add → detail → edit → soft-delete flow",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 960,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:35:08.684Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-android-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    },
                    {
                      "workerIndex": 961,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:35:17.928Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-android-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-71bf3c4f9362506e6e3d",
              "file": "vendors.spec.js",
              "line": 284,
              "column": 3
            },
            {
              "title": "Add tab opens empty form",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1964,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8275,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:35:09.981Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    },
                    {
                      "workerIndex": 1965,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8266,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:35:19.391Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--75ccc-te-Add-tab-opens-empty-form-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-ef7dcf98d5a7397875f2",
              "file": "vendors.spec.js",
              "line": 271,
              "column": 3
            },
            {
              "title": "saving with empty name shows error toast",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1966,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8255,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:35:28.788Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    },
                    {
                      "workerIndex": 1967,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8249,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:35:38.146Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--4230f-mpty-name-shows-error-toast-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-a16db7cceb1e36e0a6c1",
              "file": "vendors.spec.js",
              "line": 278,
              "column": 3
            },
            {
              "title": "full add → detail → edit → soft-delete flow",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1968,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8250,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:35:47.523Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-desktop-chrome/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    },
                    {
                      "workerIndex": 1969,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8253,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:263:48",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 48,
                          "line": 263
                        },
                        "snippet": "  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 48,
                            "line": 263
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).not.\u001b[22mtoHaveText\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#t-total')\nExpected: not \u001b[32m\"—\"\u001b[39m\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"not toHaveText\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('#t-total')\u001b[22m\n\n\n  261 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  262 |     await page.goto(BASE_URL);\n> 263 |     await expect(page.locator('#t-total')).not.toHaveText('—', { timeout: 8000 });\n      |                                                ^\n  264 |   });\n  265 |\n  266 |   test.afterAll(async () => {\n    at /var/www/360lm/tests/vendors.spec.js:263:48"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:35:56.819Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-desktop-chrome-retry1/error-context.md"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--39d5c-l-→-edit-→-soft-delete-flow-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 48,
                        "line": 263
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-c5803e8dd57259d17e68",
              "file": "vendors.spec.js",
              "line": 284,
              "column": 3
            }
          ]
        },
        {
          "title": "Vendor Management — Search across all fields (client-side)",
          "file": "vendors.spec.js",
          "line": 341,
          "column": 6,
          "specs": [
            {
              "title": "search by partial phone matches",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 962,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8251,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:345:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 345
                        },
                        "snippet": "  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 345
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:345:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:35:27.211Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2e77c-ch-by-partial-phone-matches-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2e77c-ch-by-partial-phone-matches-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 345
                      }
                    },
                    {
                      "workerIndex": 963,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8233,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:345:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 345
                        },
                        "snippet": "  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 345
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:345:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:35:36.415Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2e77c-ch-by-partial-phone-matches-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2e77c-ch-by-partial-phone-matches-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 345
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-906dd1bfca2edbdfc975",
              "file": "vendors.spec.js",
              "line": 348,
              "column": 3
            },
            {
              "title": "search by alias (existing payee with aliases)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 964,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8224,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:345:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 345
                        },
                        "snippet": "  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 345
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:345:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:35:45.622Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2bd26-xisting-payee-with-aliases--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2bd26-xisting-payee-with-aliases--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 345
                      }
                    },
                    {
                      "workerIndex": 965,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8314,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:345:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 345
                        },
                        "snippet": "  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 345
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:345:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:35:54.891Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2bd26-xisting-payee-with-aliases--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2bd26-xisting-payee-with-aliases--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 345
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-4da82cd9ba1de161e681",
              "file": "vendors.spec.js",
              "line": 356,
              "column": 3
            },
            {
              "title": "search by partial phone matches",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1970,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8270,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:345:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 345
                        },
                        "snippet": "  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 345
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:345:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:36:06.318Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2e77c-ch-by-partial-phone-matches-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2e77c-ch-by-partial-phone-matches-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 345
                      }
                    },
                    {
                      "workerIndex": 1971,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:345:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 345
                        },
                        "snippet": "  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 345
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:345:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:36:15.595Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2e77c-ch-by-partial-phone-matches-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2e77c-ch-by-partial-phone-matches-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 345
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-ffe62834c70152c41134",
              "file": "vendors.spec.js",
              "line": 348,
              "column": 3
            },
            {
              "title": "search by alias (existing payee with aliases)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1972,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8256,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:345:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 345
                        },
                        "snippet": "  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 345
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:345:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:36:24.806Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2bd26-xisting-payee-with-aliases--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2bd26-xisting-payee-with-aliases--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 345
                      }
                    },
                    {
                      "workerIndex": 1973,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8216,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:345:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 345
                        },
                        "snippet": "  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 345
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  343 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  344 |     await page.goto(BASE_URL);\n> 345 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  346 |   });\n  347 |\n  348 |   test('search by partial phone matches', async ({ page }) => {\n    at /var/www/360lm/tests/vendors.spec.js:345:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:36:34.088Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2bd26-xisting-payee-with-aliases--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--2bd26-xisting-payee-with-aliases--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 345
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-ffd2ca7ccd5bf5f28752",
              "file": "vendors.spec.js",
              "line": 356,
              "column": 3
            }
          ]
        },
        {
          "title": "Vendor Management — Catalog tab (Phase 3a)",
          "file": "vendors.spec.js",
          "line": 365,
          "column": 6,
          "specs": [
            {
              "title": "HTML contains catalog tab + link button + modals",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 966,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 66,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:370:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 370
                        },
                        "snippet": "  368 |     const resp = await ctx.get(BASE_URL);\n  369 |     const html = await resp.text();\n> 370 |     expect(html).toContain('data-tab=\"catalog\"');\n      |                  ^\n  371 |     expect(html).toContain('id=\"d-panel-catalog\"');\n  372 |     expect(html).toContain('id=\"cat-list\"');\n  373 |     expect(html).toContain('id=\"modal-link-product\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 370
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  368 |     const resp = await ctx.get(BASE_URL);\n  369 |     const html = await resp.text();\n> 370 |     expect(html).toContain('data-tab=\"catalog\"');\n      |                  ^\n  371 |     expect(html).toContain('id=\"d-panel-catalog\"');\n  372 |     expect(html).toContain('id=\"cat-list\"');\n  373 |     expect(html).toContain('id=\"modal-link-product\"');\n    at /var/www/360lm/tests/vendors.spec.js:370:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:36:04.398Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fd710-alog-tab-link-button-modals-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 370
                      }
                    },
                    {
                      "workerIndex": 967,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 68,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:370:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 370
                        },
                        "snippet": "  368 |     const resp = await ctx.get(BASE_URL);\n  369 |     const html = await resp.text();\n> 370 |     expect(html).toContain('data-tab=\"catalog\"');\n      |                  ^\n  371 |     expect(html).toContain('id=\"d-panel-catalog\"');\n  372 |     expect(html).toContain('id=\"cat-list\"');\n  373 |     expect(html).toContain('id=\"modal-link-product\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 370
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  368 |     const resp = await ctx.get(BASE_URL);\n  369 |     const html = await resp.text();\n> 370 |     expect(html).toContain('data-tab=\"catalog\"');\n      |                  ^\n  371 |     expect(html).toContain('id=\"d-panel-catalog\"');\n  372 |     expect(html).toContain('id=\"cat-list\"');\n  373 |     expect(html).toContain('id=\"modal-link-product\"');\n    at /var/www/360lm/tests/vendors.spec.js:370:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:36:05.474Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fd710-alog-tab-link-button-modals-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 370
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-8037abc8117a30b31dfc",
              "file": "vendors.spec.js",
              "line": 366,
              "column": 3
            },
            {
              "title": "Catalog tab loads on detail screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 968,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8246,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:384:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 384
                        },
                        "snippet": "  382 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  383 |     await page.goto(BASE_URL);\n> 384 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  385 |     await page.locator('.v-row').first().click();\n  386 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  387 |     await expect(page.locator('#d-panel-catalog')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 384
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  382 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  383 |     await page.goto(BASE_URL);\n> 384 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  385 |     await page.locator('.v-row').first().click();\n  386 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  387 |     await expect(page.locator('#d-panel-catalog')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:384:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:36:06.431Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3b542--tab-loads-on-detail-screen-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3b542--tab-loads-on-detail-screen-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 384
                      }
                    },
                    {
                      "workerIndex": 969,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8252,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:384:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 384
                        },
                        "snippet": "  382 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  383 |     await page.goto(BASE_URL);\n> 384 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  385 |     await page.locator('.v-row').first().click();\n  386 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  387 |     await expect(page.locator('#d-panel-catalog')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 384
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  382 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  383 |     await page.goto(BASE_URL);\n> 384 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  385 |     await page.locator('.v-row').first().click();\n  386 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  387 |     await expect(page.locator('#d-panel-catalog')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:384:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:36:15.593Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3b542--tab-loads-on-detail-screen-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3b542--tab-loads-on-detail-screen-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 384
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-33c0b724ff12b08028e6",
              "file": "vendors.spec.js",
              "line": 381,
              "column": 3
            },
            {
              "title": "Link Product modal opens + shows results",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 970,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8230,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:395:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 395
                        },
                        "snippet": "  393 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  394 |     await page.goto(BASE_URL);\n> 395 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  396 |     await page.locator('.v-row').first().click();\n  397 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  398 |     await page.locator('button:has-text(\"Link a product\")').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 395
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  393 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  394 |     await page.goto(BASE_URL);\n> 395 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  396 |     await page.locator('.v-row').first().click();\n  397 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  398 |     await page.locator('button:has-text(\"Link a product\")').click();\n    at /var/www/360lm/tests/vendors.spec.js:395:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:36:24.777Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fc000-t-modal-opens-shows-results-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fc000-t-modal-opens-shows-results-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 395
                      }
                    },
                    {
                      "workerIndex": 971,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8250,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:395:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 395
                        },
                        "snippet": "  393 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  394 |     await page.goto(BASE_URL);\n> 395 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  396 |     await page.locator('.v-row').first().click();\n  397 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  398 |     await page.locator('button:has-text(\"Link a product\")').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 395
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  393 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  394 |     await page.goto(BASE_URL);\n> 395 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  396 |     await page.locator('.v-row').first().click();\n  397 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  398 |     await page.locator('button:has-text(\"Link a product\")').click();\n    at /var/www/360lm/tests/vendors.spec.js:395:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:36:33.969Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fc000-t-modal-opens-shows-results-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fc000-t-modal-opens-shows-results-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 395
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-6a17e84519d6aff6397c",
              "file": "vendors.spec.js",
              "line": 392,
              "column": 3
            },
            {
              "title": "HTML contains catalog tab + link button + modals",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1974,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 49,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:370:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 370
                        },
                        "snippet": "  368 |     const resp = await ctx.get(BASE_URL);\n  369 |     const html = await resp.text();\n> 370 |     expect(html).toContain('data-tab=\"catalog\"');\n      |                  ^\n  371 |     expect(html).toContain('id=\"d-panel-catalog\"');\n  372 |     expect(html).toContain('id=\"cat-list\"');\n  373 |     expect(html).toContain('id=\"modal-link-product\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 370
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  368 |     const resp = await ctx.get(BASE_URL);\n  369 |     const html = await resp.text();\n> 370 |     expect(html).toContain('data-tab=\"catalog\"');\n      |                  ^\n  371 |     expect(html).toContain('id=\"d-panel-catalog\"');\n  372 |     expect(html).toContain('id=\"cat-list\"');\n  373 |     expect(html).toContain('id=\"modal-link-product\"');\n    at /var/www/360lm/tests/vendors.spec.js:370:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:36:43.178Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fd710-alog-tab-link-button-modals-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 370
                      }
                    },
                    {
                      "workerIndex": 1975,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 58,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:370:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 370
                        },
                        "snippet": "  368 |     const resp = await ctx.get(BASE_URL);\n  369 |     const html = await resp.text();\n> 370 |     expect(html).toContain('data-tab=\"catalog\"');\n      |                  ^\n  371 |     expect(html).toContain('id=\"d-panel-catalog\"');\n  372 |     expect(html).toContain('id=\"cat-list\"');\n  373 |     expect(html).toContain('id=\"modal-link-product\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 370
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"catalog\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  368 |     const resp = await ctx.get(BASE_URL);\n  369 |     const html = await resp.text();\n> 370 |     expect(html).toContain('data-tab=\"catalog\"');\n      |                  ^\n  371 |     expect(html).toContain('id=\"d-panel-catalog\"');\n  372 |     expect(html).toContain('id=\"cat-list\"');\n  373 |     expect(html).toContain('id=\"modal-link-product\"');\n    at /var/www/360lm/tests/vendors.spec.js:370:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:36:43.960Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fd710-alog-tab-link-button-modals-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 370
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-aceb7eb906199c3cd7b1",
              "file": "vendors.spec.js",
              "line": 366,
              "column": 3
            },
            {
              "title": "Catalog tab loads on detail screen",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1976,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8232,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:384:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 384
                        },
                        "snippet": "  382 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  383 |     await page.goto(BASE_URL);\n> 384 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  385 |     await page.locator('.v-row').first().click();\n  386 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  387 |     await expect(page.locator('#d-panel-catalog')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 384
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  382 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  383 |     await page.goto(BASE_URL);\n> 384 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  385 |     await page.locator('.v-row').first().click();\n  386 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  387 |     await expect(page.locator('#d-panel-catalog')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:384:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:36:44.797Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3b542--tab-loads-on-detail-screen-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3b542--tab-loads-on-detail-screen-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 384
                      }
                    },
                    {
                      "workerIndex": 1977,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8394,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:384:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 384
                        },
                        "snippet": "  382 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  383 |     await page.goto(BASE_URL);\n> 384 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  385 |     await page.locator('.v-row').first().click();\n  386 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  387 |     await expect(page.locator('#d-panel-catalog')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 384
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  382 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  383 |     await page.goto(BASE_URL);\n> 384 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  385 |     await page.locator('.v-row').first().click();\n  386 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  387 |     await expect(page.locator('#d-panel-catalog')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:384:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:36:53.996Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3b542--tab-loads-on-detail-screen-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--3b542--tab-loads-on-detail-screen-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 384
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-ee849dacc30f78ed0667",
              "file": "vendors.spec.js",
              "line": 381,
              "column": 3
            },
            {
              "title": "Link Product modal opens + shows results",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1978,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8256,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:395:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 395
                        },
                        "snippet": "  393 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  394 |     await page.goto(BASE_URL);\n> 395 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  396 |     await page.locator('.v-row').first().click();\n  397 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  398 |     await page.locator('button:has-text(\"Link a product\")').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 395
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  393 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  394 |     await page.goto(BASE_URL);\n> 395 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  396 |     await page.locator('.v-row').first().click();\n  397 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  398 |     await page.locator('button:has-text(\"Link a product\")').click();\n    at /var/www/360lm/tests/vendors.spec.js:395:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:37:03.615Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fc000-t-modal-opens-shows-results-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fc000-t-modal-opens-shows-results-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 395
                      }
                    },
                    {
                      "workerIndex": 1979,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:395:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 395
                        },
                        "snippet": "  393 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  394 |     await page.goto(BASE_URL);\n> 395 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  396 |     await page.locator('.v-row').first().click();\n  397 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  398 |     await page.locator('button:has-text(\"Link a product\")').click();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 395
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  393 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  394 |     await page.goto(BASE_URL);\n> 395 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  396 |     await page.locator('.v-row').first().click();\n  397 |     await page.locator('#s-detail .chip[data-tab=\"catalog\"]').click();\n  398 |     await page.locator('button:has-text(\"Link a product\")').click();\n    at /var/www/360lm/tests/vendors.spec.js:395:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:37:12.890Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fc000-t-modal-opens-shows-results-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fc000-t-modal-opens-shows-results-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 395
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-5da9b3d50dad4d18feda",
              "file": "vendors.spec.js",
              "line": 392,
              "column": 3
            }
          ]
        },
        {
          "title": "Vendor Management — Phase 3c tabs",
          "file": "vendors.spec.js",
          "line": 406,
          "column": 6,
          "specs": [
            {
              "title": "HTML contains all 3 new tabs + panels + modal",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 972,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 64,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:411:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 411
                        },
                        "snippet": "  409 |     const resp = await ctx.get(BASE_URL);\n  410 |     const html = await resp.text();\n> 411 |     expect(html).toContain('data-tab=\"supplies\"');\n      |                  ^\n  412 |     expect(html).toContain('data-tab=\"rates\"');\n  413 |     expect(html).toContain('data-tab=\"prices\"');\n  414 |     expect(html).toContain('id=\"d-panel-supplies\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 411
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  409 |     const resp = await ctx.get(BASE_URL);\n  410 |     const html = await resp.text();\n> 411 |     expect(html).toContain('data-tab=\"supplies\"');\n      |                  ^\n  412 |     expect(html).toContain('data-tab=\"rates\"');\n  413 |     expect(html).toContain('data-tab=\"prices\"');\n  414 |     expect(html).toContain('id=\"d-panel-supplies\"');\n    at /var/www/360lm/tests/vendors.spec.js:411:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:36:43.124Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fee72-all-3-new-tabs-panels-modal-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 411
                      }
                    },
                    {
                      "workerIndex": 973,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:411:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 411
                        },
                        "snippet": "  409 |     const resp = await ctx.get(BASE_URL);\n  410 |     const html = await resp.text();\n> 411 |     expect(html).toContain('data-tab=\"supplies\"');\n      |                  ^\n  412 |     expect(html).toContain('data-tab=\"rates\"');\n  413 |     expect(html).toContain('data-tab=\"prices\"');\n  414 |     expect(html).toContain('id=\"d-panel-supplies\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 411
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  409 |     const resp = await ctx.get(BASE_URL);\n  410 |     const html = await resp.text();\n> 411 |     expect(html).toContain('data-tab=\"supplies\"');\n      |                  ^\n  412 |     expect(html).toContain('data-tab=\"rates\"');\n  413 |     expect(html).toContain('data-tab=\"prices\"');\n  414 |     expect(html).toContain('id=\"d-panel-supplies\"');\n    at /var/www/360lm/tests/vendors.spec.js:411:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:36:44.016Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fee72-all-3-new-tabs-panels-modal-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 411
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-2e311079a44217802969",
              "file": "vendors.spec.js",
              "line": 407,
              "column": 3
            },
            {
              "title": "Supplies tab opens (loads even with no data)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 974,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8221,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:432:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 432
                        },
                        "snippet": "  430 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  431 |     await page.goto(BASE_URL);\n> 432 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  433 |     await page.locator('.v-row').first().click();\n  434 |     await page.locator('#s-detail .chip[data-tab=\"supplies\"]').click();\n  435 |     await expect(page.locator('#d-panel-supplies')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 432
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  430 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  431 |     await page.goto(BASE_URL);\n> 432 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  433 |     await page.locator('.v-row').first().click();\n  434 |     await page.locator('#s-detail .chip[data-tab=\"supplies\"]').click();\n  435 |     await expect(page.locator('#d-panel-supplies')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:432:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:36:44.872Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fbaea-ns-loads-even-with-no-data--android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fbaea-ns-loads-even-with-no-data--android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 432
                      }
                    },
                    {
                      "workerIndex": 975,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8252,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:432:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 432
                        },
                        "snippet": "  430 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  431 |     await page.goto(BASE_URL);\n> 432 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  433 |     await page.locator('.v-row').first().click();\n  434 |     await page.locator('#s-detail .chip[data-tab=\"supplies\"]').click();\n  435 |     await expect(page.locator('#d-panel-supplies')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 432
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  430 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  431 |     await page.goto(BASE_URL);\n> 432 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  433 |     await page.locator('.v-row').first().click();\n  434 |     await page.locator('#s-detail .chip[data-tab=\"supplies\"]').click();\n  435 |     await expect(page.locator('#d-panel-supplies')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:432:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:36:53.988Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fbaea-ns-loads-even-with-no-data--android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fbaea-ns-loads-even-with-no-data--android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 432
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-50bd68a988ee021d3b99",
              "file": "vendors.spec.js",
              "line": 429,
              "column": 3
            },
            {
              "title": "Rate Card tab opens + Add modal works",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 976,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8286,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:443:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 443
                        },
                        "snippet": "  441 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  442 |     await page.goto(BASE_URL);\n> 443 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  444 |     await page.locator('.v-row').first().click();\n  445 |     await page.locator('#s-detail .chip[data-tab=\"rates\"]').click();\n  446 |     await expect(page.locator('#d-panel-rates')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 443
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  441 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  442 |     await page.goto(BASE_URL);\n> 443 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  444 |     await page.locator('.v-row').first().click();\n  445 |     await page.locator('#s-detail .chip[data-tab=\"rates\"]').click();\n  446 |     await expect(page.locator('#d-panel-rates')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:443:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:03.511Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--11281-d-tab-opens-Add-modal-works-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--11281-d-tab-opens-Add-modal-works-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 443
                      }
                    },
                    {
                      "workerIndex": 977,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8222,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:443:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 443
                        },
                        "snippet": "  441 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  442 |     await page.goto(BASE_URL);\n> 443 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  444 |     await page.locator('.v-row').first().click();\n  445 |     await page.locator('#s-detail .chip[data-tab=\"rates\"]').click();\n  446 |     await expect(page.locator('#d-panel-rates')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 443
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  441 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  442 |     await page.goto(BASE_URL);\n> 443 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  444 |     await page.locator('.v-row').first().click();\n  445 |     await page.locator('#s-detail .chip[data-tab=\"rates\"]').click();\n  446 |     await expect(page.locator('#d-panel-rates')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:443:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:37:12.740Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--11281-d-tab-opens-Add-modal-works-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--11281-d-tab-opens-Add-modal-works-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 443
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-dbaa7fb3fa929f227213",
              "file": "vendors.spec.js",
              "line": 440,
              "column": 3
            },
            {
              "title": "Prices tab opens",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 978,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8228,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:455:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 455
                        },
                        "snippet": "  453 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  454 |     await page.goto(BASE_URL);\n> 455 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  456 |     await page.locator('.v-row').first().click();\n  457 |     await page.locator('#s-detail .chip[data-tab=\"prices\"]').click();\n  458 |     await expect(page.locator('#d-panel-prices')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 455
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  453 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  454 |     await page.goto(BASE_URL);\n> 455 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  456 |     await page.locator('.v-row').first().click();\n  457 |     await page.locator('#s-detail .chip[data-tab=\"prices\"]').click();\n  458 |     await expect(page.locator('#d-panel-prices')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:455:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:21.890Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Phase-3c-tabs-Prices-tab-opens-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Phase-3c-tabs-Prices-tab-opens-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 455
                      }
                    },
                    {
                      "workerIndex": 979,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8243,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:455:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 455
                        },
                        "snippet": "  453 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  454 |     await page.goto(BASE_URL);\n> 455 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  456 |     await page.locator('.v-row').first().click();\n  457 |     await page.locator('#s-detail .chip[data-tab=\"prices\"]').click();\n  458 |     await expect(page.locator('#d-panel-prices')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 455
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  453 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  454 |     await page.goto(BASE_URL);\n> 455 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  456 |     await page.locator('.v-row').first().click();\n  457 |     await page.locator('#s-detail .chip[data-tab=\"prices\"]').click();\n  458 |     await expect(page.locator('#d-panel-prices')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:455:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:37:31.081Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Phase-3c-tabs-Prices-tab-opens-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Phase-3c-tabs-Prices-tab-opens-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 455
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-f94fa1068564b8eab113",
              "file": "vendors.spec.js",
              "line": 452,
              "column": 3
            },
            {
              "title": "HTML contains all 3 new tabs + panels + modal",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1980,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 48,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:411:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 411
                        },
                        "snippet": "  409 |     const resp = await ctx.get(BASE_URL);\n  410 |     const html = await resp.text();\n> 411 |     expect(html).toContain('data-tab=\"supplies\"');\n      |                  ^\n  412 |     expect(html).toContain('data-tab=\"rates\"');\n  413 |     expect(html).toContain('data-tab=\"prices\"');\n  414 |     expect(html).toContain('id=\"d-panel-supplies\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 411
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  409 |     const resp = await ctx.get(BASE_URL);\n  410 |     const html = await resp.text();\n> 411 |     expect(html).toContain('data-tab=\"supplies\"');\n      |                  ^\n  412 |     expect(html).toContain('data-tab=\"rates\"');\n  413 |     expect(html).toContain('data-tab=\"prices\"');\n  414 |     expect(html).toContain('id=\"d-panel-supplies\"');\n    at /var/www/360lm/tests/vendors.spec.js:411:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:37:22.106Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fee72-all-3-new-tabs-panels-modal-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 411
                      }
                    },
                    {
                      "workerIndex": 1981,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 60,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vendors.spec.js:411:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 18,
                          "line": 411
                        },
                        "snippet": "  409 |     const resp = await ctx.get(BASE_URL);\n  410 |     const html = await resp.text();\n> 411 |     expect(html).toContain('data-tab=\"supplies\"');\n      |                  ^\n  412 |     expect(html).toContain('data-tab=\"rates\"');\n  413 |     expect(html).toContain('data-tab=\"prices\"');\n  414 |     expect(html).toContain('id=\"d-panel-supplies\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 18,
                            "line": 411
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"data-tab=\\\"supplies\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  409 |     const resp = await ctx.get(BASE_URL);\n  410 |     const html = await resp.text();\n> 411 |     expect(html).toContain('data-tab=\"supplies\"');\n      |                  ^\n  412 |     expect(html).toContain('data-tab=\"rates\"');\n  413 |     expect(html).toContain('data-tab=\"prices\"');\n  414 |     expect(html).toContain('id=\"d-panel-supplies\"');\n    at /var/www/360lm/tests/vendors.spec.js:411:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:37:23.014Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fee72-all-3-new-tabs-panels-modal-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 18,
                        "line": 411
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-c52db4208ca5058a5a1d",
              "file": "vendors.spec.js",
              "line": 407,
              "column": 3
            },
            {
              "title": "Supplies tab opens (loads even with no data)",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1982,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8237,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:432:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 432
                        },
                        "snippet": "  430 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  431 |     await page.goto(BASE_URL);\n> 432 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  433 |     await page.locator('.v-row').first().click();\n  434 |     await page.locator('#s-detail .chip[data-tab=\"supplies\"]').click();\n  435 |     await expect(page.locator('#d-panel-supplies')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 432
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  430 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  431 |     await page.goto(BASE_URL);\n> 432 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  433 |     await page.locator('.v-row').first().click();\n  434 |     await page.locator('#s-detail .chip[data-tab=\"supplies\"]').click();\n  435 |     await expect(page.locator('#d-panel-supplies')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:432:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:37:23.851Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fbaea-ns-loads-even-with-no-data--desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fbaea-ns-loads-even-with-no-data--desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 432
                      }
                    },
                    {
                      "workerIndex": 1983,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8245,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:432:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 432
                        },
                        "snippet": "  430 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  431 |     await page.goto(BASE_URL);\n> 432 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  433 |     await page.locator('.v-row').first().click();\n  434 |     await page.locator('#s-detail .chip[data-tab=\"supplies\"]').click();\n  435 |     await expect(page.locator('#d-panel-supplies')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 432
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  430 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  431 |     await page.goto(BASE_URL);\n> 432 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  433 |     await page.locator('.v-row').first().click();\n  434 |     await page.locator('#s-detail .chip[data-tab=\"supplies\"]').click();\n  435 |     await expect(page.locator('#d-panel-supplies')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:432:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:37:33.201Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fbaea-ns-loads-even-with-no-data--desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--fbaea-ns-loads-even-with-no-data--desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 432
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-3b6526886e0b06dd4e43",
              "file": "vendors.spec.js",
              "line": 429,
              "column": 3
            },
            {
              "title": "Rate Card tab opens + Add modal works",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1984,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8236,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:443:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 443
                        },
                        "snippet": "  441 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  442 |     await page.goto(BASE_URL);\n> 443 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  444 |     await page.locator('.v-row').first().click();\n  445 |     await page.locator('#s-detail .chip[data-tab=\"rates\"]').click();\n  446 |     await expect(page.locator('#d-panel-rates')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 443
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  441 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  442 |     await page.goto(BASE_URL);\n> 443 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  444 |     await page.locator('.v-row').first().click();\n  445 |     await page.locator('#s-detail .chip[data-tab=\"rates\"]').click();\n  446 |     await expect(page.locator('#d-panel-rates')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:443:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:37:42.382Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--11281-d-tab-opens-Add-modal-works-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--11281-d-tab-opens-Add-modal-works-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 443
                      }
                    },
                    {
                      "workerIndex": 1985,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8269,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:443:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 443
                        },
                        "snippet": "  441 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  442 |     await page.goto(BASE_URL);\n> 443 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  444 |     await page.locator('.v-row').first().click();\n  445 |     await page.locator('#s-detail .chip[data-tab=\"rates\"]').click();\n  446 |     await expect(page.locator('#d-panel-rates')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 443
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  441 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  442 |     await page.goto(BASE_URL);\n> 443 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  444 |     await page.locator('.v-row').first().click();\n  445 |     await page.locator('#s-detail .chip[data-tab=\"rates\"]').click();\n  446 |     await expect(page.locator('#d-panel-rates')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:443:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:37:51.596Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--11281-d-tab-opens-Add-modal-works-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management--11281-d-tab-opens-Add-modal-works-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 443
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-449a22611a55b28a9c66",
              "file": "vendors.spec.js",
              "line": 440,
              "column": 3
            },
            {
              "title": "Prices tab opens",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1986,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8359,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:455:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 455
                        },
                        "snippet": "  453 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  454 |     await page.goto(BASE_URL);\n> 455 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  456 |     await page.locator('.v-row').first().click();\n  457 |     await page.locator('#s-detail .chip[data-tab=\"prices\"]').click();\n  458 |     await expect(page.locator('#d-panel-prices')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 455
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  453 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  454 |     await page.goto(BASE_URL);\n> 455 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  456 |     await page.locator('.v-row').first().click();\n  457 |     await page.locator('#s-detail .chip[data-tab=\"prices\"]').click();\n  458 |     await expect(page.locator('#d-panel-prices')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:455:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:00.943Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Phase-3c-tabs-Prices-tab-opens-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Phase-3c-tabs-Prices-tab-opens-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 455
                      }
                    },
                    {
                      "workerIndex": 1987,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8231,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n    at /var/www/360lm/tests/vendors.spec.js:455:50",
                        "location": {
                          "file": "/var/www/360lm/tests/vendors.spec.js",
                          "column": 50,
                          "line": 455
                        },
                        "snippet": "  453 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  454 |     await page.goto(BASE_URL);\n> 455 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  456 |     await page.locator('.v-row').first().click();\n  457 |     await page.locator('#s-detail .chip[data-tab=\"prices\"]').click();\n  458 |     await expect(page.locator('#d-panel-prices')).toBeVisible();"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vendors.spec.js",
                            "column": 50,
                            "line": 455
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('.v-row').first()\nExpected: visible\nTimeout: 8000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 8000ms\u001b[22m\n\u001b[2m  - waiting for locator('.v-row').first()\u001b[22m\n\n\n  453 |     await injectSession(page, 'harish', 'Harish Kumar Lal');\n  454 |     await page.goto(BASE_URL);\n> 455 |     await expect(page.locator('.v-row').first()).toBeVisible({ timeout: 8000 });\n      |                                                  ^\n  456 |     await page.locator('.v-row').first().click();\n  457 |     await page.locator('#s-detail .chip[data-tab=\"prices\"]').click();\n  458 |     await expect(page.locator('#d-panel-prices')).toBeVisible();\n    at /var/www/360lm/tests/vendors.spec.js:455:50"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:38:10.346Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Phase-3c-tabs-Prices-tab-opens-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vendors-Vendor-Management-—-Phase-3c-tabs-Prices-tab-opens-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vendors.spec.js",
                        "column": 50,
                        "line": 455
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "00432bf7b36e113a2571-8e8592a518629d7fa17b",
              "file": "vendors.spec.js",
              "line": 452,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "vrs.spec.js",
      "file": "vrs.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Vehicle Requisition — Smoke",
          "file": "vrs.spec.js",
          "line": 9,
          "column": 6,
          "specs": [
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 980,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1078,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:40.283Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "0c785c59ff61d83b7a53-3cf3313cb0c13bd5a656",
              "file": "vrs.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 980,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 204,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is present', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is present', async ({ page }) => {\n    at /var/www/360lm/tests/vrs.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:41.541Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-manifest-json-returns-200-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-manifest-json-returns-200-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 981,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 285,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is present', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is present', async ({ page }) => {\n    at /var/www/360lm/tests/vrs.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:37:42.526Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-manifest-json-returns-200-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-manifest-json-returns-200-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "0c785c59ff61d83b7a53-664923050b990ea9e9fd",
              "file": "vrs.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 982,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 264,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('vrs-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('vrs-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/vrs.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:43.753Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-sw-js-is-present-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-sw-js-is-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 983,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 245,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('vrs-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('vrs-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/vrs.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:37:45.011Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-sw-js-is-present-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-sw-js-is-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "0c785c59ff61d83b7a53-d975811ad8c3bff860a1",
              "file": "vrs.spec.js",
              "line": 22,
              "column": 3
            },
            {
              "title": "page or hub loads",
              "ok": true,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1988,
                      "parallelIndex": 0,
                      "status": "passed",
                      "duration": 1057,
                      "errors": [],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:19.578Z",
                      "annotations": [],
                      "attachments": []
                    }
                  ],
                  "status": "expected"
                }
              ],
              "id": "0c785c59ff61d83b7a53-2d79338ffc1ea8f2d5d9",
              "file": "vrs.spec.js",
              "line": 10,
              "column": 3
            },
            {
              "title": "manifest.json returns 200",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1988,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 204,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is present', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is present', async ({ page }) => {\n    at /var/www/360lm/tests/vrs.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:20.797Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-manifest-json-returns-200-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-manifest-json-returns-200-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    },
                    {
                      "workerIndex": 1989,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 323,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:19:28",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 28,
                          "line": 19
                        },
                        "snippet": "  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is present', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 28,
                            "line": 19
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoBe\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // Object.is equality\u001b[22m\n\nExpected: \u001b[32m200\u001b[39m\nReceived: \u001b[31m404\u001b[39m\n\n  17 |   test('manifest.json returns 200', async ({ page }) => {\n  18 |     const resp = await page.goto(BASE_URL + 'manifest.json');\n> 19 |     expect(resp?.status()).toBe(200);\n     |                            ^\n  20 |   });\n  21 |\n  22 |   test('sw.js is present', async ({ page }) => {\n    at /var/www/360lm/tests/vrs.spec.js:19:28"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:38:21.844Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-manifest-json-returns-200-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 28,
                        "line": 19
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "0c785c59ff61d83b7a53-66bdc5e0b6679d1bed6a",
              "file": "vrs.spec.js",
              "line": 17,
              "column": 3
            },
            {
              "title": "sw.js is present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1990,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 249,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('vrs-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('vrs-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/vrs.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:23.254Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-sw-js-is-present-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-sw-js-is-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    },
                    {
                      "workerIndex": 1991,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 275,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:25:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 25
                        },
                        "snippet": "  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('vrs-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 25
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"vrs-v\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  23 |     const resp = await page.goto(BASE_URL + 'sw.js');\n  24 |     const text = await resp?.text() || '';\n> 25 |     expect(text).toContain('vrs-v');\n     |                  ^\n  26 |   });\n  27 | });\n  28 |\n    at /var/www/360lm/tests/vrs.spec.js:25:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:38:24.495Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-sw-js-is-present-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-Smoke-sw-js-is-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 25
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "0c785c59ff61d83b7a53-9b892b68a623b5386abd",
              "file": "vrs.spec.js",
              "line": 22,
              "column": 3
            }
          ]
        },
        {
          "title": "Vehicle Requisition — HTML Structure",
          "file": "vrs.spec.js",
          "line": 29,
          "column": 6,
          "specs": [
            {
              "title": "all screens present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 984,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-splash\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-login\"');\n  42 |     expect(html).toContain('id=\"screen-mine\"');\n  43 |     expect(html).toContain('id=\"screen-approvals\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-splash\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-login\"');\n  42 |     expect(html).toContain('id=\"screen-mine\"');\n  43 |     expect(html).toContain('id=\"screen-approvals\"');\n    at /var/www/360lm/tests/vrs.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:46.192Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-HTML-Structure-all-screens-present-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    },
                    {
                      "workerIndex": 985,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 12,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-splash\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-login\"');\n  42 |     expect(html).toContain('id=\"screen-mine\"');\n  43 |     expect(html).toContain('id=\"screen-approvals\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-splash\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-login\"');\n  42 |     expect(html).toContain('id=\"screen-mine\"');\n  43 |     expect(html).toContain('id=\"screen-approvals\"');\n    at /var/www/360lm/tests/vrs.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:37:47.148Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-HTML-Structure-all-screens-present-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "0c785c59ff61d83b7a53-29a2d7ef0d6a4cf6c5eb",
              "file": "vrs.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub navigation present on all screens",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 986,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 13,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:48:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 48
                        },
                        "snippet": "  46 |\n  47 |   test('hub navigation present on all screens', () => {\n> 48 |     expect(html).toContain('href=\"/hub/\"');\n     |                  ^\n  49 |     expect(html).toContain('🏠');\n  50 |   });\n  51 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 48
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  46 |\n  47 |   test('hub navigation present on all screens', () => {\n> 48 |     expect(html).toContain('href=\"/hub/\"');\n     |                  ^\n  49 |     expect(html).toContain('🏠');\n  50 |   });\n  51 | });\n    at /var/www/360lm/tests/vrs.spec.js:48:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:48.086Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—--bd047-tion-present-on-all-screens-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 48
                      }
                    },
                    {
                      "workerIndex": 987,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 12,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:48:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 48
                        },
                        "snippet": "  46 |\n  47 |   test('hub navigation present on all screens', () => {\n> 48 |     expect(html).toContain('href=\"/hub/\"');\n     |                  ^\n  49 |     expect(html).toContain('🏠');\n  50 |   });\n  51 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 48
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  46 |\n  47 |   test('hub navigation present on all screens', () => {\n> 48 |     expect(html).toContain('href=\"/hub/\"');\n     |                  ^\n  49 |     expect(html).toContain('🏠');\n  50 |   });\n  51 | });\n    at /var/www/360lm/tests/vrs.spec.js:48:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:37:49.066Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—--bd047-tion-present-on-all-screens-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 48
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "0c785c59ff61d83b7a53-90d3fbe99d93242a07fb",
              "file": "vrs.spec.js",
              "line": 47,
              "column": 3
            },
            {
              "title": "all screens present",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1992,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-splash\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-login\"');\n  42 |     expect(html).toContain('id=\"screen-mine\"');\n  43 |     expect(html).toContain('id=\"screen-approvals\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-splash\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-login\"');\n  42 |     expect(html).toContain('id=\"screen-mine\"');\n  43 |     expect(html).toContain('id=\"screen-approvals\"');\n    at /var/www/360lm/tests/vrs.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:25.726Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-HTML-Structure-all-screens-present-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    },
                    {
                      "workerIndex": 1993,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 9,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:40:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 40
                        },
                        "snippet": "  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-splash\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-login\"');\n  42 |     expect(html).toContain('id=\"screen-mine\"');\n  43 |     expect(html).toContain('id=\"screen-approvals\"');"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 40
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"id=\\\"screen-splash\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  38 |\n  39 |   test('all screens present', () => {\n> 40 |     expect(html).toContain('id=\"screen-splash\"');\n     |                  ^\n  41 |     expect(html).toContain('id=\"screen-login\"');\n  42 |     expect(html).toContain('id=\"screen-mine\"');\n  43 |     expect(html).toContain('id=\"screen-approvals\"');\n    at /var/www/360lm/tests/vrs.spec.js:40:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:38:26.562Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—-HTML-Structure-all-screens-present-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 40
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "0c785c59ff61d83b7a53-b9e52074f451c0212a8f",
              "file": "vrs.spec.js",
              "line": 39,
              "column": 3
            },
            {
              "title": "hub navigation present on all screens",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1994,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 10,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:48:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 48
                        },
                        "snippet": "  46 |\n  47 |   test('hub navigation present on all screens', () => {\n> 48 |     expect(html).toContain('href=\"/hub/\"');\n     |                  ^\n  49 |     expect(html).toContain('🏠');\n  50 |   });\n  51 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 48
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  46 |\n  47 |   test('hub navigation present on all screens', () => {\n> 48 |     expect(html).toContain('href=\"/hub/\"');\n     |                  ^\n  49 |     expect(html).toContain('🏠');\n  50 |   });\n  51 | });\n    at /var/www/360lm/tests/vrs.spec.js:48:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:27.445Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—--bd047-tion-present-on-all-screens-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 48
                      }
                    },
                    {
                      "workerIndex": 1995,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 11,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n    at /var/www/360lm/tests/vrs.spec.js:48:18",
                        "location": {
                          "file": "/var/www/360lm/tests/vrs.spec.js",
                          "column": 18,
                          "line": 48
                        },
                        "snippet": "  46 |\n  47 |   test('hub navigation present on all screens', () => {\n> 48 |     expect(html).toContain('href=\"/hub/\"');\n     |                  ^\n  49 |     expect(html).toContain('🏠');\n  50 |   });\n  51 | });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/vrs.spec.js",
                            "column": 18,
                            "line": 48
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mreceived\u001b[39m\u001b[2m).\u001b[22mtoContain\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m) // indexOf\u001b[22m\n\nExpected substring: \u001b[32m\"href=\\\"/hub/\\\"\"\u001b[39m\nReceived string:    \u001b[31m\"404 page not found\u001b[39m\n\u001b[31m\"\u001b[39m\n\n  46 |\n  47 |   test('hub navigation present on all screens', () => {\n> 48 |     expect(html).toContain('href=\"/hub/\"');\n     |                  ^\n  49 |     expect(html).toContain('🏠');\n  50 |   });\n  51 | });\n    at /var/www/360lm/tests/vrs.spec.js:48:18"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:38:28.288Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/vrs-Vehicle-Requisition-—--bd047-tion-present-on-all-screens-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/vrs.spec.js",
                        "column": 18,
                        "line": 48
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "0c785c59ff61d83b7a53-1217a4d7774fe005d8e6",
              "file": "vrs.spec.js",
              "line": 47,
              "column": 3
            }
          ]
        }
      ]
    },
    {
      "title": "yagya-portfolio.spec.js",
      "file": "yagya-portfolio.spec.js",
      "column": 0,
      "line": 0,
      "specs": [],
      "suites": [
        {
          "title": "Yagya Portfolio — Page Load",
          "file": "yagya-portfolio.spec.js",
          "line": 23,
          "column": 6,
          "specs": [
            {
              "title": "page loads with correct title",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 989,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8321,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    13 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    13 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:30:24",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 24,
                          "line": 30
                        },
                        "snippet": "  28 |   test('page loads with correct title', async ({ page }) => {\n  29 |     await page.goto(BASE_URL);\n> 30 |     await expect(page).toHaveTitle(/Yagya Narang/);\n     |                        ^\n  31 |   });\n  32 |\n  33 |   test('hero video loads and is not errored', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 24,
                            "line": 30
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    13 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n\n\n  28 |   test('page loads with correct title', async ({ page }) => {\n  29 |     await page.goto(BASE_URL);\n> 30 |     await expect(page).toHaveTitle(/Yagya Narang/);\n     |                        ^\n  31 |   });\n  32 |\n  33 |   test('hero video loads and is not errored', async ({ page }) => {\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:30:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:37:51.090Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e6010-ge-loads-with-correct-title-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e6010-ge-loads-with-correct-title-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 24,
                        "line": 30
                      }
                    },
                    {
                      "workerIndex": 990,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 8844,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    12 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    12 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:30:24",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 24,
                          "line": 30
                        },
                        "snippet": "  28 |   test('page loads with correct title', async ({ page }) => {\n  29 |     await page.goto(BASE_URL);\n> 30 |     await expect(page).toHaveTitle(/Yagya Narang/);\n     |                        ^\n  31 |   });\n  32 |\n  33 |   test('hero video loads and is not errored', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 24,
                            "line": 30
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    12 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n\n\n  28 |   test('page loads with correct title', async ({ page }) => {\n  29 |     await page.goto(BASE_URL);\n> 30 |     await expect(page).toHaveTitle(/Yagya Narang/);\n     |                        ^\n  31 |   });\n  32 |\n  33 |   test('hero video loads and is not errored', async ({ page }) => {\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:30:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:38:00.666Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e6010-ge-loads-with-correct-title-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e6010-ge-loads-with-correct-title-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 24,
                        "line": 30
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-62e23461dd9e25fef39b",
              "file": "yagya-portfolio.spec.js",
              "line": 28,
              "column": 3
            },
            {
              "title": "hero video loads and is not errored",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 991,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7762,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:36:27",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 27,
                          "line": 36
                        },
                        "snippet": "  34 |     await page.goto(BASE_URL);\n  35 |     const heroVid = page.locator('#reel');\n> 36 |     await expect(heroVid).toBeAttached();\n     |                           ^\n  37 |     const errored = await heroVid.evaluate(v => v.error !== null);\n  38 |     expect(errored).toBe(false);\n  39 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 27,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n\n\n  34 |     await page.goto(BASE_URL);\n  35 |     const heroVid = page.locator('#reel');\n> 36 |     await expect(heroVid).toBeAttached();\n     |                           ^\n  37 |     const errored = await heroVid.evaluate(v => v.error !== null);\n  38 |     expect(errored).toBe(false);\n  39 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:36:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:38:10.557Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-4b381-eo-loads-and-is-not-errored-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-4b381-eo-loads-and-is-not-errored-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 27,
                        "line": 36
                      }
                    },
                    {
                      "workerIndex": 992,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7415,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:36:27",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 27,
                          "line": 36
                        },
                        "snippet": "  34 |     await page.goto(BASE_URL);\n  35 |     const heroVid = page.locator('#reel');\n> 36 |     await expect(heroVid).toBeAttached();\n     |                           ^\n  37 |     const errored = await heroVid.evaluate(v => v.error !== null);\n  38 |     expect(errored).toBe(false);\n  39 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 27,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n\n\n  34 |     await page.goto(BASE_URL);\n  35 |     const heroVid = page.locator('#reel');\n> 36 |     await expect(heroVid).toBeAttached();\n     |                           ^\n  37 |     const errored = await heroVid.evaluate(v => v.error !== null);\n  38 |     expect(errored).toBe(false);\n  39 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:36:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:38:19.288Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-4b381-eo-loads-and-is-not-errored-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-4b381-eo-loads-and-is-not-errored-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 27,
                        "line": 36
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-823202116afb8216ba0a",
              "file": "yagya-portfolio.spec.js",
              "line": 33,
              "column": 3
            },
            {
              "title": "page loads with correct title",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1997,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7735,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    13 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    13 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:30:24",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 24,
                          "line": 30
                        },
                        "snippet": "  28 |   test('page loads with correct title', async ({ page }) => {\n  29 |     await page.goto(BASE_URL);\n> 30 |     await expect(page).toHaveTitle(/Yagya Narang/);\n     |                        ^\n  31 |   });\n  32 |\n  33 |   test('hero video loads and is not errored', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 24,
                            "line": 30
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    13 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n\n\n  28 |   test('page loads with correct title', async ({ page }) => {\n  29 |     await page.goto(BASE_URL);\n> 30 |     await expect(page).toHaveTitle(/Yagya Narang/);\n     |                        ^\n  31 |   });\n  32 |\n  33 |   test('hero video loads and is not errored', async ({ page }) => {\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:30:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:30.384Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e6010-ge-loads-with-correct-title-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e6010-ge-loads-with-correct-title-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 24,
                        "line": 30
                      }
                    },
                    {
                      "workerIndex": 1998,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7570,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    13 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    13 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:30:24",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 24,
                          "line": 30
                        },
                        "snippet": "  28 |   test('page loads with correct title', async ({ page }) => {\n  29 |     await page.goto(BASE_URL);\n> 30 |     await expect(page).toHaveTitle(/Yagya Narang/);\n     |                        ^\n  31 |   });\n  32 |\n  33 |   test('hero video loads and is not errored', async ({ page }) => {"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 24,
                            "line": 30
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mpage\u001b[39m\u001b[2m).\u001b[22mtoHaveTitle\u001b[2m(\u001b[22m\u001b[32mexpected\u001b[39m\u001b[2m)\u001b[22m failed\n\nExpected pattern: \u001b[32m/Yagya Narang/\u001b[39m\nReceived string:  \u001b[31m\"YAGYA NARANG — Cinematographer\"\u001b[39m\nTimeout: 5000ms\n\nCall log:\n\u001b[2m  - Expect \"toHaveTitle\" with timeout 5000ms\u001b[22m\n\u001b[2m    13 × unexpected value \"YAGYA NARANG — Cinematographer\"\u001b[22m\n\n\n  28 |   test('page loads with correct title', async ({ page }) => {\n  29 |     await page.goto(BASE_URL);\n> 30 |     await expect(page).toHaveTitle(/Yagya Narang/);\n     |                        ^\n  31 |   });\n  32 |\n  33 |   test('hero video loads and is not errored', async ({ page }) => {\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:30:24"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:38:39.153Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e6010-ge-loads-with-correct-title-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e6010-ge-loads-with-correct-title-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 24,
                        "line": 30
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-31d4130d73ce0fda2e58",
              "file": "yagya-portfolio.spec.js",
              "line": 28,
              "column": 3
            },
            {
              "title": "hero video loads and is not errored",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 1999,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7455,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:36:27",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 27,
                          "line": 36
                        },
                        "snippet": "  34 |     await page.goto(BASE_URL);\n  35 |     const heroVid = page.locator('#reel');\n> 36 |     await expect(heroVid).toBeAttached();\n     |                           ^\n  37 |     const errored = await heroVid.evaluate(v => v.error !== null);\n  38 |     expect(errored).toBe(false);\n  39 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 27,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n\n\n  34 |     await page.goto(BASE_URL);\n  35 |     const heroVid = page.locator('#reel');\n> 36 |     await expect(heroVid).toBeAttached();\n     |                           ^\n  37 |     const errored = await heroVid.evaluate(v => v.error !== null);\n  38 |     expect(errored).toBe(false);\n  39 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:36:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:38:47.731Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-4b381-eo-loads-and-is-not-errored-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-4b381-eo-loads-and-is-not-errored-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 27,
                        "line": 36
                      }
                    },
                    {
                      "workerIndex": 2000,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7633,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:36:27",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 27,
                          "line": 36
                        },
                        "snippet": "  34 |     await page.goto(BASE_URL);\n  35 |     const heroVid = page.locator('#reel');\n> 36 |     await expect(heroVid).toBeAttached();\n     |                           ^\n  37 |     const errored = await heroVid.evaluate(v => v.error !== null);\n  38 |     expect(errored).toBe(false);\n  39 |   });"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 27,
                            "line": 36
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeAttached\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#reel')\nExpected: attached\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeAttached\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#reel')\u001b[22m\n\n\n  34 |     await page.goto(BASE_URL);\n  35 |     const heroVid = page.locator('#reel');\n> 36 |     await expect(heroVid).toBeAttached();\n     |                           ^\n  37 |     const errored = await heroVid.evaluate(v => v.error !== null);\n  38 |     expect(errored).toBe(false);\n  39 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:36:27"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:38:56.162Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-4b381-eo-loads-and-is-not-errored-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-4b381-eo-loads-and-is-not-errored-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 27,
                        "line": 36
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-9c6d75e66d22427b8d04",
              "file": "yagya-portfolio.spec.js",
              "line": 33,
              "column": 3
            }
          ]
        },
        {
          "title": "Yagya Portfolio — Film Section",
          "file": "yagya-portfolio.spec.js",
          "line": 42,
          "column": 6,
          "specs": [
            {
              "title": "film list renders all work items",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 993,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30318,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 49
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  47 |   test('film list renders all work items', async ({ page }) => {\n  48 |     await page.goto(BASE_URL);\n> 49 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  50 |     const items = page.locator('#film-list .film-item');\n  51 |     await expect(items).toHaveCount(37);\n  52 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:49:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:38:27.759Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fcb83-list-renders-all-work-items-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fcb83-list-renders-all-work-items-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 994,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 31967,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 49
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  47 |   test('film list renders all work items', async ({ page }) => {\n  48 |     await page.goto(BASE_URL);\n> 49 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  50 |     const items = page.locator('#film-list .film-item');\n  51 |     await expect(items).toHaveCount(37);\n  52 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:49:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:38:59.063Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fcb83-list-renders-all-work-items-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fcb83-list-renders-all-work-items-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-67e57c9262b28152812b",
              "file": "yagya-portfolio.spec.js",
              "line": 47,
              "column": 3
            },
            {
              "title": "archive-4 video loads without error when clicked",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 995,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 31057,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 56
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  54 |   test('archive-4 video loads without error when clicked', async ({ page }) => {\n  55 |     await page.goto(BASE_URL);\n> 56 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  57 |\n  58 |     // Click \"Archive IV\" — index 30 (0-based) in the WORK array\n  59 |     // Using nth(30) to avoid substring match with \"Archive I\"\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:56:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:39:32.271Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-9c090--without-error-when-clicked-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-9c090--without-error-when-clicked-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 996,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30476,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 56
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  54 |   test('archive-4 video loads without error when clicked', async ({ page }) => {\n  55 |     await page.goto(BASE_URL);\n> 56 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  57 |\n  58 |     // Click \"Archive IV\" — index 30 (0-based) in the WORK array\n  59 |     // Using nth(30) to avoid substring match with \"Archive I\"\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:56:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:40:04.947Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-9c090--without-error-when-clicked-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-9c090--without-error-when-clicked-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-b32b104997d4d6314363",
              "file": "yagya-portfolio.spec.js",
              "line": 54,
              "column": 3
            },
            {
              "title": "default reel video in film section is not errored",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 997,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30423,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 82
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  80 |   test('default reel video in film section is not errored', async ({ page }) => {\n  81 |     await page.goto(BASE_URL);\n> 82 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  83 |     const vid = page.locator('#film-player-wrap video');\n  84 |     await expect(vid).toBeAttached({ timeout: 5000 });\n  85 |     const errored = await vid.evaluate(v => v.error !== null);\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:82:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:40:36.592Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-69eaa-film-section-is-not-errored-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-69eaa-film-section-is-not-errored-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 998,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30344,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 82
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  80 |   test('default reel video in film section is not errored', async ({ page }) => {\n  81 |     await page.goto(BASE_URL);\n> 82 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  83 |     const vid = page.locator('#film-player-wrap video');\n  84 |     await expect(vid).toBeAttached({ timeout: 5000 });\n  85 |     const errored = await vid.evaluate(v => v.error !== null);\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:82:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:41:08.148Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-69eaa-film-section-is-not-errored-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-69eaa-film-section-is-not-errored-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-1f39f2483ad344acf5c9",
              "file": "yagya-portfolio.spec.js",
              "line": 80,
              "column": 3
            },
            {
              "title": "film list renders all work items",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 2001,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30357,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 49
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  47 |   test('film list renders all work items', async ({ page }) => {\n  48 |     await page.goto(BASE_URL);\n> 49 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  50 |     const items = page.locator('#film-list .film-item');\n  51 |     await expect(items).toHaveCount(37);\n  52 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:49:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:39:05.940Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fcb83-list-renders-all-work-items-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fcb83-list-renders-all-work-items-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 2002,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30640,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 49
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  47 |   test('film list renders all work items', async ({ page }) => {\n  48 |     await page.goto(BASE_URL);\n> 49 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  50 |     const items = page.locator('#film-list .film-item');\n  51 |     await expect(items).toHaveCount(37);\n  52 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:49:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:39:37.383Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fcb83-list-renders-all-work-items-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fcb83-list-renders-all-work-items-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-221b5c195c154a94597a",
              "file": "yagya-portfolio.spec.js",
              "line": 47,
              "column": 3
            },
            {
              "title": "archive-4 video loads without error when clicked",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 2003,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30367,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 56
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  54 |   test('archive-4 video loads without error when clicked', async ({ page }) => {\n  55 |     await page.goto(BASE_URL);\n> 56 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  57 |\n  58 |     // Click \"Archive IV\" — index 30 (0-based) in the WORK array\n  59 |     // Using nth(30) to avoid substring match with \"Archive I\"\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:56:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:40:09.215Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-9c090--without-error-when-clicked-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-9c090--without-error-when-clicked-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 2004,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30302,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 56
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  54 |   test('archive-4 video loads without error when clicked', async ({ page }) => {\n  55 |     await page.goto(BASE_URL);\n> 56 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  57 |\n  58 |     // Click \"Archive IV\" — index 30 (0-based) in the WORK array\n  59 |     // Using nth(30) to avoid substring match with \"Archive I\"\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:56:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:40:40.759Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-9c090--without-error-when-clicked-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-9c090--without-error-when-clicked-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-fb237f7528aabff1d79b",
              "file": "yagya-portfolio.spec.js",
              "line": 54,
              "column": 3
            },
            {
              "title": "default reel video in film section is not errored",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 2005,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30384,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 82
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  80 |   test('default reel video in film section is not errored', async ({ page }) => {\n  81 |     await page.goto(BASE_URL);\n> 82 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  83 |     const vid = page.locator('#film-player-wrap video');\n  84 |     await expect(vid).toBeAttached({ timeout: 5000 });\n  85 |     const errored = await vid.evaluate(v => v.error !== null);\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:82:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:41:12.052Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-69eaa-film-section-is-not-errored-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-69eaa-film-section-is-not-errored-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 2006,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30406,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 82
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#film')\u001b[22m\n\n\n  80 |   test('default reel video in film section is not errored', async ({ page }) => {\n  81 |     await page.goto(BASE_URL);\n> 82 |     await page.locator('#film').scrollIntoViewIfNeeded();\n     |                                 ^\n  83 |     const vid = page.locator('#film-player-wrap video');\n  84 |     await expect(vid).toBeAttached({ timeout: 5000 });\n  85 |     const errored = await vid.evaluate(v => v.error !== null);\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:82:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:41:43.525Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-69eaa-film-section-is-not-errored-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-69eaa-film-section-is-not-errored-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-c464779d5356cb9f8fa8",
              "file": "yagya-portfolio.spec.js",
              "line": 80,
              "column": 3
            }
          ]
        },
        {
          "title": "Yagya Portfolio — Navigation",
          "file": "yagya-portfolio.spec.js",
          "line": 90,
          "column": 6,
          "specs": [
            {
              "title": "overlay nav opens and closes",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 999,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30298,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 97
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#burg')\u001b[22m\n\n\n   95 |   test('overlay nav opens and closes', async ({ page }) => {\n   96 |     await page.goto(BASE_URL);\n>  97 |     await page.locator('#burg').click();\n      |                                 ^\n   98 |     await expect(page.locator('#overlay')).toHaveClass(/open/);\n   99 |     await page.locator('#burg').click();\n  100 |     await expect(page.locator('#overlay')).not.toHaveClass(/open/);\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:97:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:41:39.533Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e554d-verlay-nav-opens-and-closes-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e554d-verlay-nav-opens-and-closes-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1000,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30336,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 97
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#burg')\u001b[22m\n\n\n   95 |   test('overlay nav opens and closes', async ({ page }) => {\n   96 |     await page.goto(BASE_URL);\n>  97 |     await page.locator('#burg').click();\n      |                                 ^\n   98 |     await expect(page.locator('#overlay')).toHaveClass(/open/);\n   99 |     await page.locator('#burg').click();\n  100 |     await expect(page.locator('#overlay')).not.toHaveClass(/open/);\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:97:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:42:10.839Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e554d-verlay-nav-opens-and-closes-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e554d-verlay-nav-opens-and-closes-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-403727cb6aab9db3f7b9",
              "file": "yagya-portfolio.spec.js",
              "line": 95,
              "column": 3
            },
            {
              "title": "dot nav is visible on desktop",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 1001,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7586,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:105:44",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 44,
                          "line": 105
                        },
                        "snippet": "  103 |   test('dot nav is visible on desktop', async ({ page }) => {\n  104 |     await page.goto(BASE_URL);\n> 105 |     await expect(page.locator('#dot-nav')).toBeVisible();\n      |                                            ^\n  106 |   });\n  107 | });\n  108 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 44,
                            "line": 105
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n\n\n  103 |   test('dot nav is visible on desktop', async ({ page }) => {\n  104 |     await page.goto(BASE_URL);\n> 105 |     await expect(page.locator('#dot-nav')).toBeVisible();\n      |                                            ^\n  106 |   });\n  107 | });\n  108 |\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:105:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:42:42.281Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-7098e-t-nav-is-visible-on-desktop-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-7098e-t-nav-is-visible-on-desktop-android-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 44,
                        "line": 105
                      }
                    },
                    {
                      "workerIndex": 1002,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7740,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:105:44",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 44,
                          "line": 105
                        },
                        "snippet": "  103 |   test('dot nav is visible on desktop', async ({ page }) => {\n  104 |     await page.goto(BASE_URL);\n> 105 |     await expect(page.locator('#dot-nav')).toBeVisible();\n      |                                            ^\n  106 |   });\n  107 | });\n  108 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 44,
                            "line": 105
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n\n\n  103 |   test('dot nav is visible on desktop', async ({ page }) => {\n  104 |     await page.goto(BASE_URL);\n> 105 |     await expect(page.locator('#dot-nav')).toBeVisible();\n      |                                            ^\n  106 |   });\n  107 | });\n  108 |\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:105:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:42:51.007Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-7098e-t-nav-is-visible-on-desktop-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-7098e-t-nav-is-visible-on-desktop-android-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 44,
                        "line": 105
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-61f895c9407d6c6bc952",
              "file": "yagya-portfolio.spec.js",
              "line": 103,
              "column": 3
            },
            {
              "title": "overlay nav opens and closes",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 2007,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30383,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 97
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#burg')\u001b[22m\n\n\n   95 |   test('overlay nav opens and closes', async ({ page }) => {\n   96 |     await page.goto(BASE_URL);\n>  97 |     await page.locator('#burg').click();\n      |                                 ^\n   98 |     await expect(page.locator('#overlay')).toHaveClass(/open/);\n   99 |     await page.locator('#burg').click();\n  100 |     await expect(page.locator('#overlay')).not.toHaveClass(/open/);\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:97:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:42:14.991Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e554d-verlay-nav-opens-and-closes-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e554d-verlay-nav-opens-and-closes-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 2008,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30347,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 33,
                            "line": 97
                          },
                          "message": "Error: locator.click: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#burg')\u001b[22m\n\n\n   95 |   test('overlay nav opens and closes', async ({ page }) => {\n   96 |     await page.goto(BASE_URL);\n>  97 |     await page.locator('#burg').click();\n      |                                 ^\n   98 |     await expect(page.locator('#overlay')).toHaveClass(/open/);\n   99 |     await page.locator('#burg').click();\n  100 |     await expect(page.locator('#overlay')).not.toHaveClass(/open/);\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:97:33"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:42:46.429Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e554d-verlay-nav-opens-and-closes-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-e554d-verlay-nav-opens-and-closes-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-1540cebcaca0f9b60cb0",
              "file": "yagya-portfolio.spec.js",
              "line": 95,
              "column": 3
            },
            {
              "title": "dot nav is visible on desktop",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 2009,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7559,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:105:44",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 44,
                          "line": 105
                        },
                        "snippet": "  103 |   test('dot nav is visible on desktop', async ({ page }) => {\n  104 |     await page.goto(BASE_URL);\n> 105 |     await expect(page.locator('#dot-nav')).toBeVisible();\n      |                                            ^\n  106 |   });\n  107 | });\n  108 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 44,
                            "line": 105
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n\n\n  103 |   test('dot nav is visible on desktop', async ({ page }) => {\n  104 |     await page.goto(BASE_URL);\n> 105 |     await expect(page.locator('#dot-nav')).toBeVisible();\n      |                                            ^\n  106 |   });\n  107 | });\n  108 |\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:105:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:43:17.873Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-7098e-t-nav-is-visible-on-desktop-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-7098e-t-nav-is-visible-on-desktop-desktop-chrome/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 44,
                        "line": 105
                      }
                    },
                    {
                      "workerIndex": 2010,
                      "parallelIndex": 0,
                      "status": "failed",
                      "duration": 7847,
                      "error": {
                        "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n",
                        "stack": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:105:44",
                        "location": {
                          "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                          "column": 44,
                          "line": 105
                        },
                        "snippet": "  103 |   test('dot nav is visible on desktop', async ({ page }) => {\n  104 |     await page.goto(BASE_URL);\n> 105 |     await expect(page.locator('#dot-nav')).toBeVisible();\n      |                                            ^\n  106 |   });\n  107 | });\n  108 |"
                      },
                      "errors": [
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 44,
                            "line": 105
                          },
                          "message": "Error: \u001b[2mexpect(\u001b[22m\u001b[31mlocator\u001b[39m\u001b[2m).\u001b[22mtoBeVisible\u001b[2m(\u001b[22m\u001b[2m)\u001b[22m failed\n\nLocator: locator('#dot-nav')\nExpected: visible\nTimeout: 5000ms\nError: element(s) not found\n\nCall log:\n\u001b[2m  - Expect \"toBeVisible\" with timeout 5000ms\u001b[22m\n\u001b[2m  - waiting for locator('#dot-nav')\u001b[22m\n\n\n  103 |   test('dot nav is visible on desktop', async ({ page }) => {\n  104 |     await page.goto(BASE_URL);\n> 105 |     await expect(page.locator('#dot-nav')).toBeVisible();\n      |                                            ^\n  106 |   });\n  107 | });\n  108 |\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:105:44"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:43:26.619Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-7098e-t-nav-is-visible-on-desktop-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-7098e-t-nav-is-visible-on-desktop-desktop-chrome-retry1/error-context.md"
                        }
                      ],
                      "errorLocation": {
                        "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                        "column": 44,
                        "line": 105
                      }
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-2697fd9cb2d0a9f78ade",
              "file": "yagya-portfolio.spec.js",
              "line": 103,
              "column": 3
            }
          ]
        },
        {
          "title": "Yagya Portfolio — Photography",
          "file": "yagya-portfolio.spec.js",
          "line": 109,
          "column": 6,
          "specs": [
            {
              "title": "polaroid grid renders",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 1003,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30391,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 34,
                            "line": 116
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#india')\u001b[22m\n\n\n  114 |   test('polaroid grid renders', async ({ page }) => {\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.locator('#india').scrollIntoViewIfNeeded();\n      |                                  ^\n  117 |     const pols = page.locator('#pol-grid .pol');\n  118 |     await expect(pols).toHaveCount(23);\n  119 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:116:34"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:42:59.899Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fad55-raphy-polaroid-grid-renders-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fad55-raphy-polaroid-grid-renders-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1004,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30744,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 34,
                            "line": 116
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#india')\u001b[22m\n\n\n  114 |   test('polaroid grid renders', async ({ page }) => {\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.locator('#india').scrollIntoViewIfNeeded();\n      |                                  ^\n  117 |     const pols = page.locator('#pol-grid .pol');\n  118 |     await expect(pols).toHaveCount(23);\n  119 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:116:34"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:43:31.399Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fad55-raphy-polaroid-grid-renders-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fad55-raphy-polaroid-grid-renders-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-25534f57b346d9a925f7",
              "file": "yagya-portfolio.spec.js",
              "line": 114,
              "column": 3
            },
            {
              "title": "lightbox opens on polaroid click",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "android-chrome",
                  "projectName": "android-chrome",
                  "results": [
                    {
                      "workerIndex": 1005,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30353,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 34,
                            "line": 123
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#india')\u001b[22m\n\n\n  121 |   test('lightbox opens on polaroid click', async ({ page }) => {\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('#india').scrollIntoViewIfNeeded();\n      |                                  ^\n  124 |     await page.locator('#pol-grid .pol').first().click();\n  125 |     await expect(page.locator('#lb')).toHaveClass(/on/);\n  126 |     await page.locator('#lb-x').click();\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:123:34"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-15T21:44:03.512Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fe7a6-box-opens-on-polaroid-click-android-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fe7a6-box-opens-on-polaroid-click-android-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 1006,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30532,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 34,
                            "line": 123
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#india')\u001b[22m\n\n\n  121 |   test('lightbox opens on polaroid click', async ({ page }) => {\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('#india').scrollIntoViewIfNeeded();\n      |                                  ^\n  124 |     await page.locator('#pol-grid .pol').first().click();\n  125 |     await expect(page.locator('#lb')).toHaveClass(/on/);\n  126 |     await page.locator('#lb-x').click();\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:123:34"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-15T21:44:34.920Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fe7a6-box-opens-on-polaroid-click-android-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fe7a6-box-opens-on-polaroid-click-android-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-aec31759ac34cf6c9c1b",
              "file": "yagya-portfolio.spec.js",
              "line": 121,
              "column": 3
            },
            {
              "title": "polaroid grid renders",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 2011,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30488,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 34,
                            "line": 116
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#india')\u001b[22m\n\n\n  114 |   test('polaroid grid renders', async ({ page }) => {\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.locator('#india').scrollIntoViewIfNeeded();\n      |                                  ^\n  117 |     const pols = page.locator('#pol-grid .pol');\n  118 |     await expect(pols).toHaveCount(23);\n  119 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:116:34"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:43:35.487Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fad55-raphy-polaroid-grid-renders-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fad55-raphy-polaroid-grid-renders-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 2012,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30298,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 34,
                            "line": 116
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#india')\u001b[22m\n\n\n  114 |   test('polaroid grid renders', async ({ page }) => {\n  115 |     await page.goto(BASE_URL);\n> 116 |     await page.locator('#india').scrollIntoViewIfNeeded();\n      |                                  ^\n  117 |     const pols = page.locator('#pol-grid .pol');\n  118 |     await expect(pols).toHaveCount(23);\n  119 |   });\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:116:34"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:44:07.204Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fad55-raphy-polaroid-grid-renders-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fad55-raphy-polaroid-grid-renders-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-f0ab86b6c96b27dd2476",
              "file": "yagya-portfolio.spec.js",
              "line": 114,
              "column": 3
            },
            {
              "title": "lightbox opens on polaroid click",
              "ok": false,
              "tags": [],
              "tests": [
                {
                  "timeout": 30000,
                  "annotations": [],
                  "expectedStatus": "passed",
                  "projectId": "desktop-chrome",
                  "projectName": "desktop-chrome",
                  "results": [
                    {
                      "workerIndex": 2013,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30438,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 34,
                            "line": 123
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#india')\u001b[22m\n\n\n  121 |   test('lightbox opens on polaroid click', async ({ page }) => {\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('#india').scrollIntoViewIfNeeded();\n      |                                  ^\n  124 |     await page.locator('#pol-grid .pol').first().click();\n  125 |     await expect(page.locator('#lb')).toHaveClass(/on/);\n  126 |     await page.locator('#lb-x').click();\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:123:34"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 0,
                      "startTime": "2026-09-16T00:44:38.530Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fe7a6-box-opens-on-polaroid-click-desktop-chrome/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fe7a6-box-opens-on-polaroid-click-desktop-chrome/error-context.md"
                        }
                      ]
                    },
                    {
                      "workerIndex": 2014,
                      "parallelIndex": 0,
                      "status": "timedOut",
                      "duration": 30403,
                      "error": {
                        "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m",
                        "stack": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                      },
                      "errors": [
                        {
                          "message": "\u001b[31mTest timeout of 30000ms exceeded.\u001b[39m"
                        },
                        {
                          "location": {
                            "file": "/var/www/360lm/tests/yagya-portfolio.spec.js",
                            "column": 34,
                            "line": 123
                          },
                          "message": "Error: locator.scrollIntoViewIfNeeded: Test timeout of 30000ms exceeded.\nCall log:\n\u001b[2m  - waiting for locator('#india')\u001b[22m\n\n\n  121 |   test('lightbox opens on polaroid click', async ({ page }) => {\n  122 |     await page.goto(BASE_URL);\n> 123 |     await page.locator('#india').scrollIntoViewIfNeeded();\n      |                                  ^\n  124 |     await page.locator('#pol-grid .pol').first().click();\n  125 |     await expect(page.locator('#lb')).toHaveClass(/on/);\n  126 |     await page.locator('#lb-x').click();\n    at /var/www/360lm/tests/yagya-portfolio.spec.js:123:34"
                        }
                      ],
                      "stdout": [],
                      "stderr": [],
                      "retry": 1,
                      "startTime": "2026-09-16T00:45:10.070Z",
                      "annotations": [],
                      "attachments": [
                        {
                          "name": "screenshot",
                          "contentType": "image/png",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fe7a6-box-opens-on-polaroid-click-desktop-chrome-retry1/test-failed-1.png"
                        },
                        {
                          "name": "error-context",
                          "contentType": "text/markdown",
                          "path": "/var/www/360lm/test-results/yagya-portfolio-Yagya-Port-fe7a6-box-opens-on-polaroid-click-desktop-chrome-retry1/error-context.md"
                        }
                      ]
                    }
                  ],
                  "status": "unexpected"
                }
              ],
              "id": "671c2e36d04453aae33a-1f6741bcb8108351fbf8",
              "file": "yagya-portfolio.spec.js",
              "line": 121,
              "column": 3
            }
          ]
        }
      ]
    }
  ],
  "errors": [],
  "stats": {
    "startTime": "2026-09-15T18:45:07.864Z",
    "duration": 21632885.336,
    "expected": 675,
    "skipped": 182,
    "unexpected": 1006,
    "flaky": 1
  }
}